Skip to content

Commit 9a51cf9

Browse files
authored
Merge pull request #18 from Mordil/more-basic-commands
More basic commands
2 parents e4e32fd + 4c7fe40 commit 9a51cf9

File tree

5 files changed

+287
-107
lines changed

5 files changed

+287
-107
lines changed

Sources/NIORedis/Commands/BasicCommands.swift

Lines changed: 130 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,35 @@ import Foundation
22
import NIO
33

44
extension RedisCommandExecutor {
5+
/// Echos the provided message through the Redis instance.
6+
///
7+
/// See [https://redis.io/commands/echo](https://redis.io/commands/echo)
8+
/// - Parameter message: The message to echo.
9+
/// - Returns: The message sent with the command.
10+
public func echo(_ message: String) -> EventLoopFuture<String> {
11+
return send(command: "ECHO", with: [message])
12+
.mapFromRESP()
13+
}
14+
15+
/// Pings the server, which will respond with a message.
16+
///
17+
/// See [https://redis.io/commands/ping](https://redis.io/commands/ping)
18+
/// - Parameter with: The optional message that the server should respond with.
19+
/// - Returns: The provided message or Redis' default response of `"PONG"`.
20+
public func ping(with message: String? = nil) -> EventLoopFuture<String> {
21+
let arg = message != nil ? [message] : []
22+
return send(command: "PING", with: arg)
23+
.mapFromRESP()
24+
}
25+
26+
/// Request for authentication in a password-protected Redis server.
27+
///
28+
/// [https://redis.io/commands/auth](https://redis.io/commands/auth)
29+
public func authorize(with password: String) -> EventLoopFuture<Void> {
30+
return send(command: "AUTH", with: [password])
31+
.map { _ in return () }
32+
}
33+
534
/// Select the Redis logical database having the specified zero-based numeric index.
635
/// New connections always use the database `0`.
736
///
@@ -11,26 +40,28 @@ extension RedisCommandExecutor {
1140
.map { _ in return () }
1241
}
1342

14-
/// Request for authentication in a password-protected Redis server.
43+
/// Swaps the data of two Redis database by their index ID.
1544
///
16-
/// [https://redis.io/commands/auth](https://redis.io/commands/auth)
17-
public func authorize(with password: String) -> EventLoopFuture<Void> {
18-
return send(command: "AUTH", with: [password])
19-
.map { _ in return () }
45+
/// See [https://redis.io/commands/swapdb](https://redis.io/commands/swapdb)
46+
/// - Parameters:
47+
/// - firstIndex: The index of the first database.
48+
/// - secondIndex: The index of the second database.
49+
/// - Returns: `true` if the swap was successful.
50+
public func swapdb(firstIndex: Int, secondIndex: Int) -> EventLoopFuture<Bool> {
51+
return send(command: "SWAPDB", with: [firstIndex, secondIndex])
52+
.mapFromRESP(to: String.self)
53+
.map { return $0 == "OK" }
2054
}
55+
}
2156

57+
extension RedisCommandExecutor {
2258
/// Removes the specified keys. A key is ignored if it does not exist.
2359
///
2460
/// [https://redis.io/commands/del](https://redis.io/commands/del)
2561
/// - Returns: A future number of keys that were removed.
2662
public func delete(_ keys: String...) -> EventLoopFuture<Int> {
2763
return send(command: "DEL", with: keys)
28-
.flatMapThrowing { res in
29-
guard let count = res.int else {
30-
throw RedisError(identifier: "delete", reason: "Unexpected response: \(res)")
31-
}
32-
return count
33-
}
64+
.mapFromRESP()
3465
}
3566

3667
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.
@@ -42,12 +73,8 @@ extension RedisCommandExecutor {
4273
/// - Returns: A future bool indicating if the expiration was set or not.
4374
public func expire(_ key: String, after deadline: Int) -> EventLoopFuture<Bool> {
4475
return send(command: "EXPIRE", with: [key, deadline.description])
45-
.flatMapThrowing { res in
46-
guard let value = res.int else {
47-
throw RedisError(identifier: "expire", reason: "Unexpected response: \(res)")
48-
}
49-
return value == 1
50-
}
76+
.mapFromRESP(to: Int.self)
77+
.map { return $0 == 1 }
5178
}
5279

5380
/// Get the value of a key.
@@ -60,6 +87,16 @@ extension RedisCommandExecutor {
6087
.map { return $0.string }
6188
}
6289

90+
/// Returns the values of all specified keys, using `.null` to represent non-existant values.
91+
///
92+
/// See [https://redis.io/commands/mget](https://redis.io/commands/mget)
93+
public func mget(_ keys: [String]) -> EventLoopFuture<[RESPValue]> {
94+
assert(keys.count > 0, "At least 1 key should be provided.")
95+
96+
return send(command: "MGET", with: keys)
97+
.mapFromRESP()
98+
}
99+
63100
/// Set key to hold the string value.
64101
/// If key already holds a value, it is overwritten, regardless of its type.
65102
/// Any previous time to live associated with the key is discarded on successful SET operation.
@@ -70,39 +107,85 @@ extension RedisCommandExecutor {
70107
.map { _ in return () }
71108
}
72109

73-
/// Echos the provided message through the Redis instance.
74-
/// - Parameter message: The message to echo.
75-
/// - Returns: The message sent with the command.
76-
public func echo(_ message: String) -> EventLoopFuture<String> {
77-
return send(command: "ECHO", with: [message])
78-
.flatMapThrowing {
79-
guard let response = $0.string else { throw RedisError.respConversion(to: String.self) }
80-
return response
81-
}
110+
/// Sets each key to the respective new value, overwriting existing values.
111+
///
112+
/// - Note: Use `msetnx` if you don't want to overwrite values.
113+
///
114+
/// See [https://redis.io/commands/mset](https://redis.io/commands/mset)
115+
public func mset(_ operations: [String: RESPValueConvertible]) -> EventLoopFuture<Void> {
116+
assert(operations.count > 0, "At least 1 key-value pair should be provided.")
117+
118+
let args = _convertMSET(operations)
119+
return send(command: "MSET", with: args)
120+
.map { _ in return () }
82121
}
83122

84-
/// Pings the server, which will respond with a message.
85-
/// - Parameter with: The optional message that the server should respond with.
86-
/// - Returns: The provided message or Redis' default response of `"PONG"`.
87-
public func ping(with message: String? = nil) -> EventLoopFuture<String> {
88-
let arg = message != nil ? [message] : []
89-
return send(command: "PING", with: arg)
90-
.flatMapThrowing {
91-
guard let response = $0.string else { throw RedisError.respConversion(to: String.self) }
92-
return response
93-
}
123+
/// If every key does not exist, sets each key to the respective new value.
124+
///
125+
/// See [https://redis.io/commands/msetnx](https://redis.io/commands/msetnx)
126+
public func msetnx(_ operations: [String: RESPValueConvertible]) -> EventLoopFuture<Bool> {
127+
assert(operations.count > 0, "At least 1 key-value pair should be provided.")
128+
129+
let args = _convertMSET(operations)
130+
return send(command: "MSETNX", with: args)
131+
.mapFromRESP(to: Int.self)
132+
.map { return $0 == 1 }
94133
}
95134

96-
/// Swaps the data of two Redis database by their index ID.
97-
/// - Parameters:
98-
/// - firstIndex: The index of the first database.
99-
/// - secondIndex: The index of the second database.
100-
/// - Returns: `true` if the swap was successful.
101-
public func swapdb(firstIndex: Int, secondIndex: Int) -> EventLoopFuture<Bool> {
102-
return send(command: "SWAPDB", with: [firstIndex, secondIndex])
103-
.flatMapThrowing {
104-
guard let response = $0.string else { throw RedisError.respConversion(to: String.self) }
105-
return response == "OK"
106-
}
135+
@inline(__always)
136+
private func _convertMSET(_ source: [String: RESPValueConvertible]) -> [RESPValueConvertible] {
137+
return source.reduce(into: [RESPValueConvertible](), { (result, element) in
138+
result.append(element.key)
139+
result.append(element.value)
140+
})
141+
}
142+
}
143+
144+
extension RedisCommandExecutor {
145+
/// Increments the stored value by 1 and returns the new value.
146+
///
147+
/// See [https://redis.io/commands/incr](https://redis.io/commands/incr)
148+
/// - Returns: The new value after the operation.
149+
public func increment(_ key: String) -> EventLoopFuture<Int> {
150+
return send(command: "INCR", with: [key])
151+
.mapFromRESP()
152+
}
153+
154+
/// Increments the stored value by the amount desired and returns the new value.
155+
///
156+
/// See [https://redis.io/commands/incrby](https://redis.io/commands/incrby)
157+
/// - Returns: The new value after the operation.
158+
public func increment(_ key: String, by count: Int) -> EventLoopFuture<Int> {
159+
return send(command: "INCRBY", with: [key, count])
160+
.mapFromRESP()
161+
}
162+
163+
/// Increments the stored value by the amount desired and returns the new value.
164+
///
165+
/// See [https://redis.io/commands/incrbyfloat](https://redis.io/commands/incrbyfloat)
166+
/// - Returns: The new value after the operation.
167+
public func increment<T: BinaryFloatingPoint>(_ key: String, by count: T) -> EventLoopFuture<T>
168+
where T: RESPValueConvertible
169+
{
170+
return send(command: "INCRBYFLOAT", with: [key, count])
171+
.mapFromRESP()
172+
}
173+
174+
/// Decrements the stored value by 1 and returns the new value.
175+
///
176+
/// See [https://redis.io/commands/decr](https://redis.io/commands/decr)
177+
/// - Returns: The new value after the operation.
178+
public func decrement(_ key: String) -> EventLoopFuture<Int> {
179+
return send(command: "DECR", with: [key])
180+
.mapFromRESP()
181+
}
182+
183+
/// Decrements the stored valye by the amount desired and returns the new value.
184+
///
185+
/// See [https://redis.io/commands/decrby](https://redis.io/commands/decrby)
186+
/// - Returns: The new value after the operation.
187+
public func decrement(_ key: String, by count: Int) -> EventLoopFuture<Int> {
188+
return send(command: "DECRBY", with: [key, count])
189+
.mapFromRESP()
107190
}
108191
}

Sources/NIORedis/Commands/SetCommands.swift

Lines changed: 26 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,16 @@ extension RedisCommandExecutor {
1919
/// - Parameter item: The element to look in the set for, stored as a `bulkString`.
2020
public func sismember(_ key: String, item: RESPValueConvertible) -> EventLoopFuture<Bool> {
2121
return send(command: "SISMEMBER", with: [key, item])
22-
.flatMapThrowing {
23-
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
24-
return result == 1
25-
}
22+
.mapFromRESP(to: Int.self)
23+
.map { return $0 == 1 }
2624
}
2725

2826
/// Returns the total count of elements in the set stored at key.
2927
///
3028
/// [https://redis.io/commands/scard](https://redis.io/commands/scard)
3129
public func scard(_ key: String) -> EventLoopFuture<Int> {
3230
return send(command: "SCARD", with: [key])
33-
.flatMapThrowing {
34-
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
35-
return count
36-
}
31+
.mapFromRESP()
3732
}
3833

3934
/// Adds the provided items to the set stored at key, returning the count of items added.
@@ -44,10 +39,7 @@ extension RedisCommandExecutor {
4439
assert(items.count > 0, "There must be at least 1 item to add.")
4540

4641
return send(command: "SADD", with: [key] + items)
47-
.flatMapThrowing {
48-
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
49-
return result
50-
}
42+
.mapFromRESP()
5143
}
5244

5345
/// Removes the provided items from the set stored at key, returning the count of items removed.
@@ -58,10 +50,7 @@ extension RedisCommandExecutor {
5850
assert(items.count > 0, "There must be at least 1 item listed to remove.")
5951

6052
return send(command: "SREM", with: [key] + items)
61-
.flatMapThrowing {
62-
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
63-
return result
64-
}
53+
.mapFromRESP()
6554
}
6655

6756
/// Randomly selects an item from the set stored at key, and removes it.
@@ -90,10 +79,7 @@ extension RedisCommandExecutor {
9079
/// [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff)
9180
public func sdiff(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
9281
return send(command: "SDIFF", with: keys)
93-
.flatMapThrowing {
94-
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
95-
return elements
96-
}
82+
.mapFromRESP()
9783
}
9884

9985
/// Functionally equivalent to `sdiff`, but instead stores the resulting set at the `destination` key
@@ -103,21 +89,15 @@ extension RedisCommandExecutor {
10389
/// - Important: If the `destination` key already exists, it is overwritten.
10490
public func sdiffstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
10591
return send(command: "SDIFFSTORE", with: [dest] + keys)
106-
.flatMapThrowing {
107-
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
108-
return count
109-
}
92+
.mapFromRESP()
11093
}
11194

11295
/// Returns the members of the set resulting from the intersection of all the given sets.
11396
///
11497
/// [https://redis.io/commands/sinter](https://redis.io/commands/sinter)
11598
public func sinter(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
11699
return send(command: "SINTER", with: keys)
117-
.flatMapThrowing {
118-
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
119-
return elements
120-
}
100+
.mapFromRESP()
121101
}
122102

123103
/// Functionally equivalent to `sinter`, but instead stores the resulting set at the `destination` key
@@ -127,10 +107,7 @@ extension RedisCommandExecutor {
127107
/// - Important: If the `destination` key already exists, it is overwritten.
128108
public func sinterstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
129109
return send(command: "SINTERSTORE", with: [dest] + keys)
130-
.flatMapThrowing {
131-
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
132-
return count
133-
}
110+
.mapFromRESP()
134111
}
135112

136113
/// Moves the `item` from the source key to the destination key.
@@ -139,21 +116,16 @@ extension RedisCommandExecutor {
139116
/// - Important: This will resolve to `true` as long as it was successfully removed from the `source` key.
140117
public func smove(item: RESPValueConvertible, fromKey source: String, toKey dest: String) -> EventLoopFuture<Bool> {
141118
return send(command: "SMOVE", with: [source, dest, item])
142-
.flatMapThrowing {
143-
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
144-
return result == 1
145-
}
119+
.mapFromRESP()
120+
.map { return $0 == 1 }
146121
}
147122

148123
/// Returns the members of the set resulting from the union of all the given keys.
149124
///
150125
/// [https://redis.io/commands/sunion](https://redis.io/commands/sunion)
151126
public func sunion(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
152127
return send(command: "SUNION", with: keys)
153-
.flatMapThrowing {
154-
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
155-
return elements
156-
}
128+
.mapFromRESP()
157129
}
158130

159131
/// Functionally equivalent to `sunion`, but instead stores the resulting set at the `destination` key
@@ -163,10 +135,7 @@ extension RedisCommandExecutor {
163135
/// - Important: If the `destination` key already exists, it is overwritten.
164136
public func sunionstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
165137
return send(command: "SUNIONSTORE", with: [dest] + keys)
166-
.flatMapThrowing {
167-
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
168-
return count
169-
}
138+
.mapFromRESP()
170139
}
171140

172141
/// Incrementally iterates over a set, returning a cursor position for additional calls with a limited collection
@@ -193,16 +162,18 @@ extension RedisCommandExecutor {
193162
args.append(c)
194163
}
195164

196-
return send(command: "SSCAN", with: args)
197-
.flatMapThrowing {
198-
guard let response = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
199-
guard
200-
let position = response[0].string,
201-
let newPosition = Int(position)
202-
else { throw RedisError.respConversion(to: Int.self) }
203-
guard let elements = response[1].array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
204-
205-
return (newPosition, elements)
206-
}
165+
let response = send(command: "SSCAN", with: args).mapFromRESP(to: [RESPValue].self)
166+
let position = response.flatMapThrowing { result -> Int in
167+
guard
168+
let value = result[0].string,
169+
let position = Int(value)
170+
else { throw RedisError(identifier: #function, reason: "Unexpected value in response: \(result[0])") }
171+
return position
172+
}
173+
let elements = response
174+
.map { return $0[1] }
175+
.mapFromRESP(to: [RESPValue].self)
176+
177+
return position.and(elements)
207178
}
208179
}

0 commit comments

Comments
 (0)