Skip to content

Commit 8e3d8f6

Browse files
committed
Revisit the SortedSet zadd command API
Motivation: While reviewing the API, the current design does not read well, and still has room for misunderstanding the actual end result of a ZADD operation. Modifications: - Rename `RedisSortedSetAddOption` to `RedisZaddInsertBehavior` and update cases to match desired use site syntax. - Add `RedisZaddReturnBehavior` enum to define how `zadd` should calculate the return value. - Update `zadd` and its overloads to support the two new enums in the form of `zadd(_:to:inserting:returning:)` Result: The more "Swifty" API will make it much more clear to developers at the call site what the actual behavior of the ZADD command will be.
1 parent 4350e01 commit 8e3d8f6

File tree

2 files changed

+77
-47
lines changed

2 files changed

+77
-47
lines changed

Sources/RediStack/Commands/SortedSetCommands.swift

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -44,77 +44,106 @@ extension RedisClient {
4444
}
4545
}
4646

47-
// MARK: General
47+
// MARK: Zadd
4848

49-
/// The supported options for the `zadd` command with Redis SortedSet types.
49+
/// The supported insert behavior for a `zadd` command with Redis SortedSet types.
50+
///
51+
/// `zadd` normally inserts all elements (`.allElements`) provided into the SortedSet, updating the score of any element that already exist in the set.
52+
///
53+
/// However, it supports two other insert behaviors:
54+
/// * `.onlyNewElements` will not update the score of any element already in the SortedSet
55+
/// * `.onlyExistingElements` will not insert any new element into the SortedSet
5056
///
5157
/// See [https://redis.io/commands/zadd#zadd-options-redis-302-or-greater](https://redis.io/commands/zadd#zadd-options-redis-302-or-greater)
52-
public enum RedisSortedSetAddOption: String {
53-
/// When adding elements, any that do not already exist in the SortedSet will be ignored and the score of the existing element will be updated.
54-
case onlyUpdateExistingElements = "XX"
55-
/// When adding elements, any that already exist in the SortedSet will be ignored and the score of the existing element will not be updated.
56-
case onlyAddNewElements = "NX"
58+
public enum RedisZaddInsertBehavior {
59+
/// Insert new elements and update the score of existing elements.
60+
case allElements
61+
/// Only insert new elements; do not update the score of existing elements.
62+
case onlyNewElements
63+
/// Only update the score of existing elements; do not insert new elements.
64+
case onlyExistingElements
65+
66+
/// Redis representation of this option.
67+
@usableFromInline
68+
internal var string: String? {
69+
switch self {
70+
case .allElements: return nil
71+
case .onlyNewElements: return "NX"
72+
case .onlyExistingElements: return "XX"
73+
}
74+
}
75+
}
76+
77+
/// The supported behavior for what a `zadd` command return value should represent.
78+
///
79+
/// `zadd` normally returns the number of new elements inserted into the set (`.insertedElementsCount`),
80+
/// but also supports the option (`.changedElementsCount`) to return the number of elements changed as a result of the command.
81+
///
82+
/// "Changed" in this context refers to both new elements that were inserted and existing elements that had their score updated.
83+
///
84+
/// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd)
85+
public enum RedisZaddReturnBehavior {
86+
/// Count both new elements that were inserted into the SortedSet and existing elements that had their score updated.
87+
case changedElementsCount
88+
/// Count only new elements that were inserted into the SortedSet.
89+
case insertedElementsCount
90+
91+
/// Redis representation of this option.
92+
@usableFromInline
93+
internal var string: String? {
94+
switch self {
95+
case .changedElementsCount: return "CH"
96+
case .insertedElementsCount: return nil
97+
}
98+
}
5799
}
58100

59101
extension RedisClient {
60102
/// Adds elements to a sorted set, assigning their score to the values provided.
61-
/// - Note: `INCR` is not supported by this library in `zadd`. Use the `zincrby(:element:in:)` method instead.
62103
///
63104
/// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd)
64105
/// - Parameters:
65106
/// - elements: A list of elements and their score to add to the sorted set.
66107
/// - key: The key of the sorted set.
67-
/// - option: An option for modifying the behavior of the command.
68-
/// - returnChangedCount: `zadd` normally returns the number of new elements added to the set,
69-
/// but setting this to `true` will instead have the command return the number of elements changed.
70-
///
71-
/// "Changed" in this context are new elements added, and elements that had their score updated.
72-
/// - Returns: The number of elements added to the sorted set, unless `returnChangedCount` was set to `true`.
108+
/// - insertBehavior: The desired behavior of handling new and existing elements in the SortedSet.
109+
/// - returnBehavior: The desired behavior of what the return value should represent.
110+
/// - Returns: If `returning` is `.changedElementsCount`, the number of elements inserted and that had their score updated. Otherwise, just the number of new elements inserted.
73111
@inlinable
74112
public func zadd<Value: RESPValueConvertible>(
75113
_ elements: [(element: Value, score: Double)],
76114
to key: RedisKey,
77-
option: RedisSortedSetAddOption? = nil,
78-
returnChangedCount: Bool = false
115+
inserting insertBehavior: RedisZaddInsertBehavior = .allElements,
116+
returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount
79117
) -> EventLoopFuture<Int> {
80118
var args: [RESPValue] = [.init(bulk: key)]
81119

82-
if let opt = option {
83-
args.append(.init(bulk: opt.rawValue))
84-
}
85-
if returnChangedCount {
86-
args.append(.init(bulk: "CH"))
87-
}
120+
args.append(convertingContentsOf: [insertBehavior.string, returnBehavior.string].compactMap({ $0 }))
88121
args.add(contentsOf: elements, overestimatedCountBeingAdded: elements.count * 2) { (array, next) in
89122
array.append(.init(bulk: next.score.description))
90123
array.append(next.element.convertedToRESPValue())
91124
}
92125

93-
return send(command: "ZADD", with: args)
126+
return self.send(command: "ZADD", with: args)
94127
.convertFromRESPValue()
95128
}
96129

97130
/// Adds elements to a sorted set, assigning their score to the values provided.
98-
/// - Note: `INCR` is not supported by this library in `zadd`. Use the `zincrby(:element:in:)` method instead.
99131
///
100132
/// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd)
101133
/// - Parameters:
102134
/// - elements: A list of elements and their score to add to the sorted set.
103135
/// - key: The key of the sorted set.
104-
/// - option: An option for modifying the behavior of the command.
105-
/// - returnChangedCount: `zadd` normally returns the number of new elements added to the set,
106-
/// but setting this to `true` will instead have the command return the number of elements changed.
107-
///
108-
/// "Changed" in this context are new elements added, and elements that had their score updated.
109-
/// - Returns: The number of elements added to the sorted set, unless `returnChangedCount` was set to `true`.
136+
/// - insertBehavior: The desired behavior of handling new and existing elements in the SortedSet.
137+
/// - returnBehavior: The desired behavior of what the return value should represent.
138+
/// - Returns: If `returning` is `.changedElementsCount`, the number of elements inserted and that had their score updated. Otherwise, just the number of new elements inserted.
110139
@inlinable
111140
public func zadd<Value: RESPValueConvertible>(
112141
_ elements: (element: Value, score: Double)...,
113142
to key: RedisKey,
114-
option: RedisSortedSetAddOption? = nil,
115-
returnChangedCount: Bool = false
143+
inserting insertBehavior: RedisZaddInsertBehavior = .allElements,
144+
returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount
116145
) -> EventLoopFuture<Int> {
117-
return self.zadd(elements, to: key, option: option, returnChangedCount: returnChangedCount)
146+
return self.zadd(elements, to: key, inserting: insertBehavior, returning: returnBehavior)
118147
}
119148

120149
/// Adds an element to a sorted set, assigning their score to the value provided.
@@ -123,23 +152,24 @@ extension RedisClient {
123152
/// - Parameters:
124153
/// - element: The element and its score to add to the sorted set.
125154
/// - key: The key of the sorted set.
126-
/// - option: An option for modifying the behavior of the command.
127-
/// - returnChangedCount: `zadd` normally returns the number of new elements added to the set,
128-
/// but setting this to `true` will instead have the command return the number of elements changed.
129-
///
130-
/// "Changed" in this context are new elements added, and elements that had their score updated.
131-
/// - Returns: `true` if the element was added or score was updated in the sorted set, depending on the `option` and `returnChangedCount` settings set.
155+
/// - insertBehavior: The desired behavior of handling new and existing elements in the SortedSet.
156+
/// - returnBehavior: The desired behavior of what the return value should represent.
157+
/// - Returns: If `returning` is `.changedElementsCount`, the number of elements inserted and that had their score updated. Otherwise, just the number of new elements inserted.
132158
@inlinable
133159
public func zadd<Value: RESPValueConvertible>(
134160
_ element: (element: Value, score: Double),
135161
to key: RedisKey,
136-
option: RedisSortedSetAddOption? = nil,
137-
returnChangedCount: Bool = false
162+
inserting insertBehavior: RedisZaddInsertBehavior = .allElements,
163+
returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount
138164
) -> EventLoopFuture<Bool> {
139-
return zadd([element], to: key, option: option, returnChangedCount: returnChangedCount)
165+
return self.zadd(element, to: key, inserting: insertBehavior, returning: returnBehavior)
140166
.map { return $0 == 1 }
141167
}
168+
}
142169

170+
// MARK: General
171+
172+
extension RedisClient {
143173
/// Gets the number of elements in a sorted set.
144174
///
145175
/// See [https://redis.io/commands/zcard](https://redis.io/commands/zcard)

Tests/RediStackIntegrationTests/Commands/SortedSetCommandsTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,19 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase {
4343
XCTAssertEqual(count, 1)
4444
count = try connection.zadd([(30, 5)], to: #function).wait()
4545
XCTAssertEqual(count, 0)
46-
count = try connection.zadd((30, 6), (31, 0), (32, 1), to: #function, option: .onlyAddNewElements).wait()
46+
count = try connection.zadd((30, 6), (31, 0), (32, 1), to: #function, inserting: .onlyNewElements).wait()
4747
XCTAssertEqual(count, 2)
4848
count = try connection.zadd(
4949
[(32, 2), (33, 3)],
5050
to: #function,
51-
option: .onlyUpdateExistingElements,
52-
returnChangedCount: true
51+
inserting: .onlyExistingElements,
52+
returning: .changedElementsCount
5353
).wait()
5454
XCTAssertEqual(count, 1)
5555

56-
var success = try connection.zadd((30, 7), to: #function, returnChangedCount: true).wait()
56+
var success = try connection.zadd((30, 7), to: #function, returning: .changedElementsCount).wait()
5757
XCTAssertTrue(success)
58-
success = try connection.zadd((30, 8), to: #function, option: .onlyAddNewElements).wait()
58+
success = try connection.zadd((30, 8), to: #function, inserting: .onlyNewElements).wait()
5959
XCTAssertFalse(success)
6060
}
6161

0 commit comments

Comments
 (0)