SortedSetCommands.swift 94.3 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 {
Nathan Harris's avatar
Nathan Harris committed
20 21 22 23
    static func _mapSortedSetResponse(
        _ response: [RESPValue],
        scoreIsFirst: Bool
    ) throws -> [(RESPValue, Double)] {
24 25 26 27 28 29 30 31
        guard response.count > 0 else { return [] }

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

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

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

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

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

        return result
    }
}

46
// MARK: Zadd
47

48 49 50 51 52 53 54
/// 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
55
///
56
/// See [https://redis.io/commands/zadd#zadd-options-redis-302-or-greater](https://redis.io/commands/zadd#zadd-options-redis-302-or-greater)
57 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
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
        }
    }
98 99
}

100
extension RedisClient {
101 102 103 104
    /// 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
105 106
    ///     - elements: A list of elements and their score to add to the sorted set.
    ///     - key: The key of the sorted set.
107 108 109
    ///     - 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.
110
    @inlinable
111 112
    public func zadd<Value: RESPValueConvertible>(
        _ elements: [(element: Value, score: Double)],
113
        to key: RedisKey,
114 115
        inserting insertBehavior: RedisZaddInsertBehavior = .allElements,
        returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount
Nathan Harris's avatar
Nathan Harris committed
116
    ) -> EventLoopFuture<Int> {
117
        var args: [RESPValue] = [.init(from: key)]
118
        
119
        args.append(convertingContentsOf: [insertBehavior.string, returnBehavior.string].compactMap({ $0 }))
120 121 122
        args.add(contentsOf: elements, overestimatedCountBeingAdded: elements.count * 2) { (array, next) in
            array.append(.init(bulk: next.score.description))
            array.append(next.element.convertedToRESPValue())
123 124
        }

125
        return self.send(command: "ZADD", with: args)
126
            .tryConverting()
127
    }
128 129 130 131 132 133 134
    
    /// 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.
135 136 137
    ///     - 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.
138 139 140
    @inlinable
    public func zadd<Value: RESPValueConvertible>(
        _ elements: (element: Value, score: Double)...,
141
        to key: RedisKey,
142 143
        inserting insertBehavior: RedisZaddInsertBehavior = .allElements,
        returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount
144
    ) -> EventLoopFuture<Int> {
145
        return self.zadd(elements, to: key, inserting: insertBehavior, returning: returnBehavior)
146
    }
147 148 149 150 151

    /// 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
152 153
    ///     - element: The element and its score to add to the sorted set.
    ///     - key: The key of the sorted set.
154 155 156
    ///     - 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.
157
    @inlinable
158 159
    public func zadd<Value: RESPValueConvertible>(
        _ element: (element: Value, score: Double),
160
        to key: RedisKey,
161 162
        inserting insertBehavior: RedisZaddInsertBehavior = .allElements,
        returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount
Nathan Harris's avatar
Nathan Harris committed
163
    ) -> EventLoopFuture<Bool> {
164
        return self.zadd(element, to: key, inserting: insertBehavior, returning: returnBehavior)
165 166
            .map { return $0 == 1 }
    }
167
}
168

169 170 171
// MARK: General

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

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

Nathan Harris's avatar
Nathan Harris committed
200
    /// Incrementally iterates over all elements in a sorted set.
201 202 203 204
    ///
    /// 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
205 206
    ///     - position: The position to start the scan from.
    ///     - match: A glob-style pattern to filter values to be selected from the result set.
Nathan Harris's avatar
Nathan Harris committed
207
    ///     - count: The number of elements to advance by. Redis default is 10.
Nathan Harris's avatar
Nathan Harris committed
208
    /// - Returns: A cursor position for additional invocations with a limited collection of elements found in the sorted set with their scores.
209
    public func zscan(
210
        _ key: RedisKey,
211
        startingFrom position: Int = 0,
Nathan Harris's avatar
Nathan Harris committed
212 213
        matching match: String? = nil,
        count: Int? = nil
Nathan Harris's avatar
Nathan Harris committed
214
    ) -> EventLoopFuture<(Int, [(RESPValue, Double)])> {
Nathan Harris's avatar
Nathan Harris committed
215
        return self._scan(command: "ZSCAN", resultType: [RESPValue].self, key, position, match, count)
216 217 218 219 220
            .flatMapThrowing {
                let values = try Self._mapSortedSetResponse($0.1, scoreIsFirst: false)
                return ($0.0, values)
            }
    }
Nathan Harris's avatar
Nathan Harris committed
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
    
    /// 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.
    ///     - match: A glob-style pattern to filter values to be selected from the result set.
    ///     - count: The number of elements to advance by. Redis default is 10.
    ///     - valueType: The type to convert the values to.
    /// - Returns: A cursor position for additional invocations with a limited collection of elements found in the sorted set with their scores.
    ///     Any element that fails the `RESPValue` conversion will be `nil`.
    @inlinable
    public func zscan<Value: RESPValueConvertible>(
        _ key: RedisKey,
        startingFrom position: Int = 0,
        matching match: String? = nil,
        count: Int? = nil,
        valueType: Value.Type
    ) -> EventLoopFuture<(Int, [(Value, Double)?])> {
        return self.zscan(key, startingFrom: position, matching: match, count: count)
            .map { (cursor, elements) in
                let mappedElements = elements.map { next -> (Value, Double)? in
                    guard let value = Value(fromRESP: next.0) else { return nil }
                    return (value, next.1)
                }
                return (cursor, mappedElements)
            }
    }
250 251 252 253
}

// MARK: Rank

254
extension RedisClient {
255 256
    /// 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
257
    /// For the inverse, see `zrevrank(of:in:)`.
258 259 260
    ///
    /// See [https://redis.io/commands/zrank](https://redis.io/commands/zrank)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
261 262
    ///     - element: The element in the sorted set to search for.
    ///     - key: The key of the sorted set to search.
263 264
    /// - Returns: The index of the element, or `nil` if the key was not found.
    @inlinable
265
    public func zrank<Value: RESPValueConvertible>(of element: Value, in key: RedisKey) -> EventLoopFuture<Int?> {
266
        let args: [RESPValue] = [
267
            .init(from: key),
268 269 270
            element.convertedToRESPValue()
        ]
        return send(command: "ZRANK", with: args)
271
            .tryConverting()
272 273 274 275
    }

    /// 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
276
    /// For the inverse, see `zrank(of:in:)`.
277 278 279
    ///
    /// See [https://redis.io/commands/zrevrank](https://redis.io/commands/zrevrank)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
280 281
    ///     - element: The element in the sorted set to search for.
    ///     - key: The key of the sorted set to search.
282 283
    /// - Returns: The index of the element, or `nil` if the key was not found.
    @inlinable
284
    public func zrevrank<Value: RESPValueConvertible>(of element: Value, in key: RedisKey) -> EventLoopFuture<Int?> {
285
        let args: [RESPValue] = [
286
            .init(from: key),
287 288 289
            element.convertedToRESPValue()
        ]
        return send(command: "ZREVRANK", with: args)
290
            .tryConverting()
291 292 293 294 295
    }
}

// MARK: Count

296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
/// 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))
    }
}

343
extension RedisClient {
344
    /// Returns the count of elements in a SortedSet with a score within the range specified (inclusive by default).
345
    ///
346 347 348 349 350 351 352 353 354 355 356
    /// 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)
357
    /// - Parameters:
358 359 360
    ///     - 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.
Nathan Harris's avatar
Nathan Harris committed
361
    public func zcount(
362
        of key: RedisKey,
363
        withScoresBetween range: (min: RedisZScoreBound, max: RedisZScoreBound)
Nathan Harris's avatar
Nathan Harris committed
364
    ) -> EventLoopFuture<Int> {
365
        guard range.min.rawValue <= range.max.rawValue else { return self.eventLoop.makeSucceededFuture(0) }
366
        let args: [RESPValue] = [
367
            .init(from: key),
368 369
            .init(bulk: range.min.description),
            .init(bulk: range.max.description)
370
        ]
371
        return self.send(command: "ZCOUNT", with: args)
372
            .tryConverting()
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 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
    
    /// 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.
    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.
    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.
    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.
    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 "-"
        }
    }
}
462

463 464 465 466 467 468 469 470 471 472 473 474 475
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.
476
    ///
477 478 479 480 481 482 483 484 485
    /// 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.
486
    /// - Parameters:
487 488 489
    ///     - 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.
490
    @inlinable
491
    public func zlexcount<Value: CustomStringConvertible>(
492
        of key: RedisKey,
493
        withValuesBetween range: (min: RedisZLexBound<Value>, max: RedisZLexBound<Value>)
Nathan Harris's avatar
Nathan Harris committed
494
    ) -> EventLoopFuture<Int> {
495
        let args: [RESPValue] = [
496
            .init(from: key),
497 498
            .init(bulk: range.min.description),
            .init(bulk: range.max.description)
499
        ]
500
        return self.send(command: "ZLEXCOUNT", with: args)
501
            .tryConverting()
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 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
    
    /// 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))
    }
553 554 555 556
}

// MARK: Pop

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

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

Nathan Harris's avatar
Nathan Harris committed
579
    /// Removes elements from a sorted set with the highest scores.
580 581 582
    ///
    /// See [https://redis.io/commands/zpopmax](https://redis.io/commands/zpopmax)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
583
    ///     - key: The key identifying the sorted set in Redis.
584
    ///     - count: The max number of elements to pop from the set.
Nathan Harris's avatar
Nathan Harris committed
585
    /// - Returns: A list of elements popped from the sorted set with their associated score.
586
    public func zpopmax(from key: RedisKey, max count: Int) -> EventLoopFuture<[(RESPValue, Double)]> {
587 588 589
        return _zpop(command: "ZPOPMAX", count, key)
    }

Nathan Harris's avatar
Nathan Harris committed
590
    /// Removes the element from a sorted set with the highest score.
591 592
    ///
    /// See [https://redis.io/commands/zpopmax](https://redis.io/commands/zpopmax)
Nathan Harris's avatar
Nathan Harris committed
593
    /// - Parameter key: The key identifying the sorted set in Redis.
594
    /// - Returns: The element and its associated score that was popped from the sorted set, or `nil` if set was empty.
595
    public func zpopmax(from key: RedisKey) -> EventLoopFuture<(RESPValue, Double)?> {
596 597 598 599
        return _zpop(command: "ZPOPMAX", nil, key)
            .map { return $0.count > 0 ? $0[0] : nil }
    }

Nathan Harris's avatar
Nathan Harris committed
600 601 602
    func _zpop(
        command: String,
        _ count: Int?,
603
        _ key: RedisKey
Nathan Harris's avatar
Nathan Harris committed
604
    ) -> EventLoopFuture<[(RESPValue, Double)]> {
605
        var args: [RESPValue] = [.init(from: key)]
606

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

610
            args.append(.init(bulk: c))
Nathan Harris's avatar
Nathan Harris committed
611
        }
612 613

        return send(command: command, with: args)
614
            .tryConverting(to: [RESPValue].self)
615 616 617 618
            .flatMapThrowing { return try Self._mapSortedSetResponse($0, scoreIsFirst: true) }
    }
}

619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
// 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.
635
    ///     - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
636 637 638 639
    /// - Returns:
    ///     The element and its associated score that was popped from the sorted set,
    ///     or `nil` if the timeout was reached.
    public func bzpopmin(
640
        from key: RedisKey,
641
        timeout: TimeAmount = .seconds(0)
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
    ) -> 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.
663
    ///     - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
664 665 666 667 668 669
    /// - 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.
    public func bzpopmin(
670
        from keys: [RedisKey],
671
        timeout: TimeAmount = .seconds(0)
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
    ) -> 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.
689
    ///     - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
690 691 692 693
    /// - Returns:
    ///     The element and its associated score that was popped from the sorted set,
    ///     or `nil` if the timeout was reached.
    public func bzpopmax(
694
        from key: RedisKey,
695
        timeout: TimeAmount = .seconds(0)
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
    ) -> 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.
717
    ///     - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
718 719 720 721 722 723
    /// - 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.
    public func bzpopmax(
724
        from keys: [RedisKey],
725
        timeout: TimeAmount = .seconds(0)
726 727 728 729 730 731
    ) -> EventLoopFuture<(String, Double, RESPValue)?> {
        return self._bzpop(command: "BZPOPMAX", keys, timeout)
    }

    func _bzpop(
        command: String,
732
        _ keys: [RedisKey],
733
        _ timeout: TimeAmount
734
    ) -> EventLoopFuture<(String, Double, RESPValue)?> {
735
        var args = keys.map(RESPValue.init)
736
        args.append(.init(bulk: timeout.seconds))
737
        
738 739 740 741 742 743
        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 }
744
                guard let response = [RESPValue](fromRESP: $0) else {
745
                    throw RedisClientError.failedRESPConversion(to: [RESPValue].self)
746 747 748 749
                }
                assert(response.count == 3, "Unexpected response size returned!")
                guard
                    let key = response[0].string,
750
                    let score = Double(fromRESP: response[1])
751
                else {
752
                    throw RedisClientError.assertionFailure(message: "Unexpected structure in response: \(response)")
753 754 755 756 757 758
                }
                return (key, score, response[2])
            }
    }
}

759 760
// MARK: Increment

761
extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
762
    /// Increments the score of the specified element in a sorted set.
763 764 765
    ///
    /// See [https://redis.io/commands/zincrby](https://redis.io/commands/zincrby)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
766 767
    ///     - amount: The amount to increment this element's score by.
    ///     - element: The element to increment.
768
    ///     - key: The key of the sorted set.
Nathan Harris's avatar
Nathan Harris committed
769
    /// - Returns: The new score of the element.
770
    @inlinable
771
    public func zincrby<Value: RESPValueConvertible>(
Nathan Harris's avatar
Nathan Harris committed
772
        _ amount: Double,
773
        element: Value,
774
        in key: RedisKey
Nathan Harris's avatar
Nathan Harris committed
775
    ) -> EventLoopFuture<Double> {
776
        let args: [RESPValue] = [
777
            .init(from: key),
778 779 780 781
            .init(bulk: amount.description),
            element.convertedToRESPValue()
        ]
        return send(command: "ZINCRBY", with: args)
782
            .tryConverting()
783 784 785 786 787
    }
}

// MARK: Intersect and Union

788 789 790 791 792 793 794 795 796 797 798 799 800 801
/// 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"
}

802
extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
803
    /// Calculates the union of two or more sorted sets and stores the result.
804 805 806 807
    /// - 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
808
    ///     - destination: The key of the new sorted set from the result.
809 810
    ///     - 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.
811
    ///     - 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
812
    /// - Returns: The number of elements in the new sorted set.
813
    public func zunionstore(
814 815
        as destination: RedisKey,
        sources: [RedisKey],
816
        weights: [Int]? = nil,
817
        aggregateMethod aggregate: RedisSortedSetAggregateMethod? = nil
Nathan Harris's avatar
Nathan Harris committed
818
    ) -> EventLoopFuture<Int> {
819 820 821
        return _zopstore(command: "ZUNIONSTORE", sources, destination, weights, aggregate)
    }

Nathan Harris's avatar
Nathan Harris committed
822
    /// Calculates the intersection of two or more sorted sets and stores the result.
823 824 825 826
    /// - 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
827
    ///     - destination: The key of the new sorted set from the result.
828 829
    ///     - 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.
830
    ///     - 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
831
    /// - Returns: The number of elements in the new sorted set.
832
    public func zinterstore(
833 834
        as destination: RedisKey,
        sources: [RedisKey],
835
        weights: [Int]? = nil,
836
        aggregateMethod aggregate: RedisSortedSetAggregateMethod? = nil
Nathan Harris's avatar
Nathan Harris committed
837
    ) -> EventLoopFuture<Int> {
838 839 840 841 842
        return _zopstore(command: "ZINTERSTORE", sources, destination, weights, aggregate)
    }

    func _zopstore(
        command: String,
843 844
        _ sources: [RedisKey],
        _ destination: RedisKey,
845
        _ weights: [Int]?,
846 847
        _ aggregate: RedisSortedSetAggregateMethod?
    ) -> EventLoopFuture<Int> {
848 849
        assert(sources.count > 0, "At least 1 source key should be provided.")

850
        var args: [RESPValue] = [
851
            .init(from: destination),
852 853 854
            .init(bulk: sources.count)
        ]
        args.append(convertingContentsOf: sources)
855 856 857 858 859

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

860 861
            args.append(.init(bulk: "WEIGHTS"))
            args.append(convertingContentsOf: w)
862 863 864
        }

        if let a = aggregate {
865
            args.append(.init(bulk: "AGGREGATE"))
866
            args.append(.init(bulk: a.rawValue))
867 868 869
        }

        return send(command: command, with: args)
870
            .tryConverting()
871 872 873 874 875
    }
}

// MARK: Range

876
extension RedisClient {
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 906 907 908 909 910 911 912
    /// 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.
    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
913
    ///
914 915 916 917
    /// To get the first element, and the last 4:
    /// ```swift
    /// client.zrange(from: "mySortedSet", indices: (-4)...0))
    /// ```
918 919
    ///
    /// See [https://redis.io/commands/zrange](https://redis.io/commands/zrange)
920 921 922 923 924 925 926
    /// - 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:)`.
927
    /// - Parameters:
928 929 930
    ///     - 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.
931
    public func zrange(
932
        from key: RedisKey,
933 934
        indices range: ClosedRange<Int>,
        includeScoresInResponse includeScores: Bool = false
Nathan Harris's avatar
Nathan Harris committed
935
    ) -> EventLoopFuture<[RESPValue]> {
936
        return self.zrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound, includeScoresInResponse: includeScores)
937
    }
938 939 940 941 942 943 944
    
    /// 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
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 1064 1065
    /// 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.
    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.
    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.
    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.
    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.
1066 1067
    ///
    /// See [https://redis.io/commands/zrevrange](https://redis.io/commands/zrevrange)
1068 1069 1070
    /// - Important: This treats the SortedSet as ordered from **high** to **low**.
    ///
    /// For the inverse, see `zrange(from:firstIndex:lastIndex:includeScoresInResponse:)`.
1071
    /// - Parameters:
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