Files
RediStack/Tests/RediStackIntegrationTests/Commands/SortedSetCommandsTests.swift
T
Nathan Harris 3c6713038d Refactor RedisCommand into a general use object
Motivation:

RediStack today represents a command as a temporary object for the purpose of writing to the channel.

While it is useful to have an object for that purpose, commands handled in this way require immediate execution
and aren't available for other purposes.

Commands can serve a better purpose as a lightweight object to support delayed execution,
so that pipeling as described in issue #63 could be possible.

Modifications:

- Add: `get` overloads for JSON codable interaction on `RedisClient`
- Add: New `RedisZRangeResultOption` type for better interactions with zrange operations that can optionally return scores
- Add: New `RedisHashFieldKey` for type-safety when working with Hash field keys much like `RedisKey`
- Change: A few API types from enums to structs for library evolution
- Change: `RedisCommandHandler` to operate on a tuple of `RESPValue, EventLoopPromise<RESPValue>` rather than `RedisCommand`
- Change: `RedisCommand` to be a generic struct with the keyword, arguments, and a transform closure to defer execution
- Change: Almost all `RedisClient` command extensions to be factory methods on `RedisCommand` instead
- Change: Many response types to be optional to avoid developers having to do `isNull` checks on their own
- Change: `RedisClient.send(command:arguments:)` to be generic with `send(_:)` as the signature
- Rename: RedisClient extensions for scan methods to be more discoverable and legible as `scanHashField`, etc.

Result:

It should be easier to support a clean pipelining API with deferred command execution,
with extensions being easier for 2nd party developers, and the maintenance overhead of all of the command extensions
should be a little lighter when it comes to changing HOW commands are sent such as adding a context parameter
2020-12-02 08:41:59 +00:00

515 lines
20 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the RediStack open source project
//
// Copyright (c) 2019-2020 RediStack project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of RediStack project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIO
@testable import RediStack
import RediStackTestUtils
import XCTest
final class SortedSetCommandsTests: RediStackIntegrationTestCase {
private static let testKey: RedisKey = "SortedSetCommandsTests"
private var key: RedisKey { return SortedSetCommandsTests.testKey }
override func setUp() {
super.setUp()
do {
var dataset: [(Int, Double)] = []
for index in 1...10 {
dataset.append((index, Double(index)))
}
_ = try connection.send(.zadd(dataset, to: SortedSetCommandsTests.testKey)).wait()
} catch {
XCTFail("Failed to create RedisConnection! \(error)")
}
}
func test_zadd() throws {
_ = try connection.send(.flushall).wait()
var count = try connection.send(.zadd([(30, 2)], to: #function)).wait()
XCTAssertEqual(count, 1)
count = try connection.send(.zadd([(30, 5)], to: #function)).wait()
XCTAssertEqual(count, 0)
count = try connection.send(.zadd((30, 6), (31, 0), (32, 1), to: #function, inserting: .onlyNewElements)).wait()
XCTAssertEqual(count, 2)
count = try connection.send(.zadd(
[(32, 2), (33, 3)],
to: #function,
inserting: .onlyExistingElements,
returning: .changedElementsCount
)).wait()
XCTAssertEqual(count, 1)
var success = try connection.send(.zadd((30, 7), to: #function, returning: .changedElementsCount)).wait()
XCTAssertTrue(success)
success = try connection.send(.zadd((30, 8), to: #function, inserting: .onlyNewElements)).wait()
XCTAssertFalse(success)
}
func test_zcard() throws {
var count = try connection.send(.zcard(of: key)).wait()
XCTAssertEqual(count, 10)
_ = try connection.send(.zadd(("foo", 0), to: key)).wait()
count = try connection.send(.zcard(of: key)).wait()
XCTAssertEqual(count, 11)
}
func test_zscore() throws {
_ = try connection.send(.flushall).wait()
var score = try connection.send(.zscore(of: 30, in: #function)).wait()
XCTAssertEqual(score, nil)
_ = try connection.send(.zadd((30, 1), to: #function)).wait()
score = try connection.send(.zscore(of: 30, in: #function)).wait()
XCTAssertEqual(score, 1)
_ = try connection.send(.zincrby(30, in: #function, by: 10)).wait()
score = try connection.send(.zscore(of: 30, in: #function)).wait()
XCTAssertEqual(score, 11)
}
// TODO: #23 -- Rework Scan Unit Test
// This is extremely flakey, and causes non-deterministic failures because of the assert on key counts
// func test_zscan() throws {
// var (cursor, results) = try connection.zscan(key, count: 5).wait()
// XCTAssertGreaterThanOrEqual(cursor, 0)
// XCTAssertGreaterThanOrEqual(results.count, 5)
//
// (_, results) = try connection.zscan(key, startingFrom: cursor, count: 8).wait()
// XCTAssertGreaterThanOrEqual(results.count, 8)
//
// (cursor, results) = try connection.zscan(key, matching: "1*").wait()
// XCTAssertEqual(cursor, 0)
// XCTAssertEqual(results.count, 2)
// XCTAssertEqual(results[0].1, 1)
//
// (cursor, results) = try connection.zscan(key, matching: "*0").wait()
// XCTAssertEqual(cursor, 0)
// XCTAssertEqual(results.count, 1)
// XCTAssertEqual(results[0].1, 10)
// }
func test_zrank() throws {
let futures = [
connection.send(.zrank(of: 1, in: key)),
connection.send(.zrank(of: 2, in: key)),
connection.send(.zrank(of: 3, in: key)),
]
let scores = try EventLoopFuture<Int?>.whenAllSucceed(futures, on: connection.eventLoop).wait()
XCTAssertEqual(scores, [0, 1, 2])
}
func test_zrevrank() throws {
let futures = [
connection.send(.zrevrank(of: 1, in: key)),
connection.send(.zrevrank(of: 2, in: key)),
connection.send(.zrevrank(of: 3, in: key)),
]
let scores = try EventLoopFuture<Int?>.whenAllSucceed(futures, on: connection.eventLoop).wait()
XCTAssertEqual(scores, [9, 8, 7])
}
func test_zcount() throws {
var count = try connection.send(.zcount(of: key, withScores: 1...3)).wait()
XCTAssertEqual(count, 3)
count = try connection.send(.zcount(of: key, withScoresBetween: (.exclusive(1), .exclusive(3)))).wait()
XCTAssertEqual(count, 1)
count = try connection.send(.zcount(of: key, withScores: 3..<8)).wait()
XCTAssertEqual(count, 5)
count = try connection.send(.zcount(of: key, withMinimumScoreOf: .exclusive(7))).wait()
XCTAssertEqual(count, 3)
count = try connection.send(.zcount(of: key, withMaximumScoreOf: 10)).wait()
XCTAssertEqual(count, 10)
count = try connection.send(.zcount(of: key, withScoresBetween: (3, 0))).wait()
XCTAssertEqual(count, 0)
}
func test_zlexcount() throws {
for i in 1...10 {
_ = try connection.send(.zadd((i, 1), to: #function)).wait()
}
var count = try connection.send(.zlexcount(of: #function, withValuesBetween: (.inclusive(1), .inclusive(3)))).wait()
XCTAssertEqual(count, 4)
count = try connection.send(.zlexcount(of: #function, withValuesBetween: (.exclusive(1), .exclusive(3)))).wait()
XCTAssertEqual(count, 2)
count = try connection.send(.zlexcount(of: #function, withMinimumValueOf: .inclusive(2))).wait()
XCTAssertEqual(count, 8)
count = try connection.send(.zlexcount(of: #function, withMaximumValueOf: .exclusive(3))).wait()
XCTAssertEqual(count, 3)
}
func test_zpopmin() throws {
let min = try connection.send(.zpopmin(from: key)).wait()
XCTAssertEqual(min?.1, 1)
_ = try connection.send(.zpopmin(from: key, max: 7)).wait()
let results = try connection.send(.zpopmin(from: key, max: 3)).wait()
XCTAssertEqual(results.count, 2)
XCTAssertEqual(results[0].1, 9)
XCTAssertEqual(results[1].1, 10)
}
func test_bzpopmin() throws {
let nilMin = try connection.send(.bzpopmin(from: #function, timeout: .seconds(1))).wait()
XCTAssertNil(nilMin)
let min1 = try connection.send(.bzpopmin(from: key)).wait()
XCTAssertEqual(min1?.0, 1)
let min2 = try connection.send(.bzpopmin(from: [#function, key])).wait()
XCTAssertEqual(min2?.0, key.rawValue)
XCTAssertEqual(min2?.1, 2)
let blockingConnection = try self.makeNewConnection()
let expectation = XCTestExpectation(description: "bzpopmin should never return")
_ = blockingConnection.send(.bzpopmin(from: #function))
.always { _ in
expectation.fulfill()
blockingConnection.close()
}
let result = XCTWaiter.wait(for: [expectation], timeout: 1)
XCTAssertEqual(result, .timedOut)
}
func test_zpopmax() throws {
let min = try connection.send(.zpopmax(from: key)).wait()
XCTAssertEqual(min?.1, 10)
_ = try connection.send(.zpopmax(from: key, max: 7)).wait()
let results = try connection.send(.zpopmax(from: key, max: 3)).wait()
XCTAssertEqual(results.count, 2)
XCTAssertEqual(results[0].1, 2)
XCTAssertEqual(results[1].1, 1)
}
func test_bzpopmax() throws {
let nilMax = try connection.send(.bzpopmax(from: #function, timeout: .seconds(1))).wait()
XCTAssertNil(nilMax)
let max1 = try connection.send(.bzpopmax(from: key)).wait()
XCTAssertEqual(max1?.0, 10)
let max2 = try connection.send(.bzpopmax(from: [#function, key])).wait()
XCTAssertEqual(max2?.0, key.rawValue)
XCTAssertEqual(max2?.1, 9)
let blockingConnection = try self.makeNewConnection()
let expectation = XCTestExpectation(description: "bzpopmax should never return")
_ = blockingConnection.send(.bzpopmax(from: #function))
.always { _ in
expectation.fulfill()
blockingConnection.close()
}
let result = XCTWaiter.wait(for: [expectation], timeout: 1)
XCTAssertEqual(result, .timedOut)
}
func test_zincrby() throws {
var score = try connection.send(.zincrby(1, in: key, by: 3_00_1398.328923)).wait()
XCTAssertEqual(score, 3_001_399.328923)
score = try connection.send(.zincrby(1, in: key, by: -201_309.1397318)).wait()
XCTAssertEqual(score, 2_800_090.1891912)
score = try connection.send(.zincrby(1, in: key, by: 20)).wait()
XCTAssertEqual(score, 2_800_110.1891912)
}
func test_zunionstore() throws {
let testKey = RedisKey(#function + #file)
_ = try connection.send(.zadd([(1, 1), (2, 2)], to: #function)).wait()
_ = try connection.send(.zadd([(3, 3), (4, 4)], to: #file)).wait()
let unionCount = try connection.send(.zunionstore(
as: testKey,
sources: [key, #function, #file],
weights: [3, 2, 1],
aggregateMethod: .max
)).wait()
XCTAssertEqual(unionCount, 10)
let rank = try connection.send(.zrank(of: 10, in: testKey)).wait()
XCTAssertEqual(rank, 9)
let score = try connection.send(.zscore(of: 10, in: testKey)).wait()
XCTAssertEqual(score, 30)
}
func test_zinterstore() throws {
_ = try connection.send(.zadd([(3, 3), (10, 10), (11, 11)], to: #function)).wait()
let unionCount = try connection.send(.zinterstore(
as: #file,
sources: [key, #function],
weights: [3, 2],
aggregateMethod: .min
)).wait()
XCTAssertEqual(unionCount, 2)
let rank = try connection.send(.zrank(of: 10, in: #file)).wait()
XCTAssertEqual(rank, 1)
let score = try connection.send(.zscore(of: 10, in: #file)).wait()
XCTAssertEqual(score, 20.0)
}
func test_zrange() throws {
var elements = try connection.send(.zrange(from: key, indices: 1...3, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 3)
elements = try connection.send(.zrange(from: key, indices: 3..<9, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 6)
elements = try connection.send(.zrange(from: key, upToIndex: 4, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 4)
elements = try connection.send(.zrange(from: key, throughIndex: 4, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 5)
elements = try connection.send(.zrange(from: key, fromIndex: 7, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 3)
let elementsAndScores = try connection.send(.zrange(from: key, firstIndex: 1, lastIndex: 3, returning: .valuesAndScores)).wait()
XCTAssertEqual(elementsAndScores.count, 3)
let values = elementsAndScores.map { Int(fromRESP: $0.0) }
XCTAssertEqual(values[0], 2)
XCTAssertEqual(values[1], 3)
XCTAssertEqual(values[2], 4)
}
func test_zrevrange() throws {
var elements = try connection.send(.zrevrange(from: key, indices: 1...3, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 3)
elements = try connection.send(.zrevrange(from: key, indices: 3..<9, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 6)
elements = try connection.send(.zrevrange(from: key, upToIndex: 4, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 4)
elements = try connection.send(.zrevrange(from: key, throughIndex: 4, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 5)
elements = try connection.send(.zrevrange(from: key, fromIndex: 7, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 3)
let elementsAndScores = try connection.send(.zrevrange(from: key, firstIndex: 1, lastIndex: 3, returning: .valuesAndScores)).wait()
XCTAssertEqual(elementsAndScores.count, 3)
let values = elementsAndScores.map { value, _ in Int(fromRESP: value) }
XCTAssertEqual(values[0], 9)
XCTAssertEqual(values[1], 8)
XCTAssertEqual(values[2], 7)
}
func test_zrangebyscore() throws {
var elements = try connection.send(.zrangebyscore(from: key, withScoresBetween: (.exclusive(1), 3), returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 2)
elements = try connection.send(.zrangebyscore(from: key, withScores: 7..<10, limitBy: (offset: 2, count: 3), returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 1)
elements = try connection.send(.zrangebyscore(from: key, withMinimumScoreOf: .exclusive(5), returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 5)
elements = try connection.send(.zrangebyscore(from: key, withMaximumScoreOf: 5, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 5)
let elementsAndScores = try connection.send(.zrangebyscore(from: key, withScores: 1...3, returning: .valuesAndScores)).wait()
XCTAssertEqual(elementsAndScores.count, 3)
let values = elementsAndScores.map { value, _ in Double(fromRESP: value) }
XCTAssertEqual(values[0], 1.0)
XCTAssertEqual(values[1], 2.0)
XCTAssertEqual(values[2], 3.0)
}
func test_zrevrangebyscore() throws {
var elements = try connection.send(.zrevrangebyscore(from: key, withScoresBetween: (.exclusive(1), 3), returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 2)
elements = try connection.send(.zrevrangebyscore(from: key, withScores: 7..<10, limitBy: (offset: 2, count: 3), returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 1)
elements = try connection.send(.zrevrangebyscore(from: key, withMinimumScoreOf: .exclusive(5), returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 5)
elements = try connection.send(.zrevrangebyscore(from: key, withMaximumScoreOf: 5, returning: .valuesOnly)).wait()
XCTAssertEqual(elements.count, 5)
let elementsAndScores = try connection.send(.zrevrangebyscore(from: key, withScores: 1...3, returning: .valuesAndScores)).wait()
XCTAssertEqual(elementsAndScores.count, 3)
let values = elementsAndScores.map(\.1)
XCTAssertEqual(values[0], 3.0)
XCTAssertEqual(values[1], 2.0)
XCTAssertEqual(values[2], 1.0)
}
func test_zrangebylex() throws {
for i in 1...10 {
_ = try connection.send(.zadd((i, 1), to: #function)).wait()
}
var elements = try connection.send(.zrangebylex(from: #function, withMinimumValueOf: .exclusive(10)))
.wait()
.map(Int.init(fromRESP:))
XCTAssertEqual(elements.count, 8)
elements = try connection.send(.zrangebylex(from: #function, withMaximumValueOf: .inclusive(5)))
.wait()
.map(Int.init(fromRESP:))
XCTAssertEqual(elements.count, 6)
elements = try connection.send(.zrangebylex(from: #function, withValuesBetween: (.inclusive(1), .inclusive(2))))
.wait()
.map(Int.init(fromRESP:))
XCTAssertEqual(elements.count, 3)
XCTAssertEqual(elements[0], 1)
XCTAssertEqual(elements[1], 10)
XCTAssertEqual(elements[2], 2)
elements = try connection.send(
.zrangebylex(
from: #function,
withValuesBetween: (.inclusive(1), .exclusive(4)),
limitBy: (offset: 1, count: 1)
)
)
.wait()
.map(Int.init(fromRESP:))
XCTAssertEqual(elements.count, 1)
XCTAssertEqual(elements[0], 10)
}
func test_zrevrangebylex() throws {
for i in 1...10 {
_ = try connection.send(.zadd((i, 1), to: #function)).wait()
}
var elements = try connection.send(.zrevrangebylex(from: #function, withMinimumValueOf: .inclusive(1)))
.wait()
.map(Int.init(fromRESP:))
XCTAssertEqual(elements.count, 10)
XCTAssertEqual(elements[0], 9)
XCTAssertEqual(elements[9], 1)
elements = try connection.send(.zrevrangebylex(from: #function, withMaximumValueOf: .exclusive(2)))
.wait()
.map(Int.init(fromRESP:))
XCTAssertEqual(elements.count, 2)
XCTAssertEqual(elements[0], 10)
elements = try connection.send(.zrevrangebylex(from: #function, withValuesBetween: (.exclusive(2), .inclusive(4))))
.wait()
.map(Int.init(fromRESP:))
XCTAssertEqual(elements.count, 2)
XCTAssertEqual(elements[0], 4)
XCTAssertEqual(elements[1], 3)
elements = try connection.send(
.zrevrangebylex(
from: #function,
withValuesBetween: (.inclusive(1), .exclusive(4)),
limitBy: (offset: 1, count: 2)
)
)
.wait()
.map(Int.init(fromRESP:))
XCTAssertEqual(elements.count, 2)
XCTAssertEqual(elements[0], 2)
}
func test_zrem() throws {
var count = try connection.send(.zrem(1, from: key)).wait()
XCTAssertEqual(count, 1)
count = try connection.send(.zrem([1], from: key)).wait()
XCTAssertEqual(count, 0)
count = try connection.send(.zrem(2, 3, 4, 5, from: key)).wait()
XCTAssertEqual(count, 4)
count = try connection.send(.zrem([5, 6, 7], from: key)).wait()
XCTAssertEqual(count, 2)
}
func test_zremrangebylex() throws {
for value in ["bar", "car", "tar"] {
_ = try connection.send(.zadd((value, 0), to: #function)).wait()
}
var count = try connection.send(.zremrangebylex(from: #function, withValuesBetween: (.exclusive("a"), .inclusive("t")))).wait()
XCTAssertEqual(count, 2)
count = try connection.send(.zremrangebylex(from: #function, withMaximumValueOf: .inclusive("t"))).wait()
XCTAssertEqual(count, 0)
count = try connection.send(.zremrangebylex(from: #function, withMinimumValueOf: .inclusive("t"))).wait()
XCTAssertEqual(count, 1)
}
func test_zremrangebyrank() throws {
var count = try connection.send(.zremrangebyrank(from: key, fromIndex: 9)).wait()
XCTAssertEqual(count, 1)
count = try connection.send(.zremrangebyrank(from: key, indices: 0...1)).wait()
XCTAssertEqual(count, 2)
count = try connection.send(.zremrangebyrank(from: key, indices: 0..<2)).wait()
XCTAssertEqual(count, 2)
count = try connection.send(.zremrangebyrank(from: key, upToIndex: 1)).wait()
XCTAssertEqual(count, 1)
count = try connection.send(.zremrangebyrank(from: key, throughIndex: 1)).wait()
XCTAssertEqual(count, 2)
count = try connection.send(.zremrangebyrank(from: key, upToIndex: 0)).wait()
XCTAssertEqual(count, 2)
}
func test_zremrangebyscore() throws {
var count = try connection.send(.zremrangebyscore(from: key, withScoresBetween: (.exclusive(8), 10))).wait()
XCTAssertEqual(count, 2)
count = try connection.send(.zremrangebyscore(from: key, withScores: 4..<7)).wait()
XCTAssertEqual(count, 3)
count = try connection.send(.zremrangebyscore(from: key, withScores: 2...3)).wait()
XCTAssertEqual(count, 2)
count = try connection.send(.zremrangebyscore(from: key, withMinimumScoreOf: .exclusive(1))).wait()
XCTAssertEqual(count, 2)
count = try connection.send(.zremrangebyscore(from: key, withMaximumScoreOf: .inclusive(1))).wait()
XCTAssertEqual(count, 1)
}
}