SetCommands.swift 11.9 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
import NIO

17 18
// MARK: General

19
extension RedisClient {
20 21
    /// Gets all of the elements contained in a set.
    /// - Note: Ordering of results are stable between multiple calls of this method to the same set.
22 23 24
    ///
    /// Results are **UNSTABLE** in regards to the ordering of insertions through the `sadd` command and this method.
    ///
25 26 27 28 29
    /// See [https://redis.io/commands/smembers](https://redis.io/commands/smembers)
    /// - Parameter key: The key of the set.
    /// - Returns: A list of elements found within the set.
    @inlinable
    public func smembers(of key: String) -> EventLoopFuture<[RESPValue]> {
30 31
        let args = [RESPValue(bulk: key)]
        return send(command: "SMEMBERS", with: args)
32
            .convertFromRESPValue()
33 34
    }

35
    /// Checks if the element is included in a set.
36
    ///
37 38 39 40 41 42
    /// See [https://redis.io/commands/sismember](https://redis.io/commands/sismember)
    /// - Parameters:
    ///     - element: The element to look for in the set.
    ///     - key: The key of the set to look in.
    /// - Returns: `true` if the element is in the set.
    @inlinable
43 44 45 46 47 48
    public func sismember<Value: RESPValueConvertible>(_ element: Value, of key: String) -> EventLoopFuture<Bool> {
        let args: [RESPValue] = [
            .init(bulk: key),
            element.convertedToRESPValue()
        ]
        return send(command: "SISMEMBER", with: args)
49
            .convertFromRESPValue(to: Int.self)
50
            .map { return $0 == 1 }
51 52
    }

53
    /// Gets the total count of elements within a set.
54
    ///
55 56 57 58 59
    /// See [https://redis.io/commands/scard](https://redis.io/commands/scard)
    /// - Parameter key: The key of the set.
    /// - Returns: The total count of elements in the set.
    @inlinable
    public func scard(of key: String) -> EventLoopFuture<Int> {
60 61
        let args = [RESPValue(bulk: key)]
        return send(command: "SCARD", with: args)
62
            .convertFromRESPValue()
63 64
    }

65
    /// Adds elements to a set.
66
    ///
67 68 69 70 71 72
    /// See [https://redis.io/commands/sadd](https://redis.io/commands/sadd)
    /// - Parameters:
    ///     - elements: The values to add to the set.
    ///     - key: The key of the set to insert into.
    /// - Returns: The number of elements that were added to the set.
    @inlinable
73
    public func sadd<Value: RESPValueConvertible>(_ elements: [Value], to key: String) -> EventLoopFuture<Int> {
74
        guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
75 76 77
        
        var args: [RESPValue] = [.init(bulk: key)]
        args.append(convertingContentsOf: elements)
78

79
        return send(command: "SADD", with: args)
80
            .convertFromRESPValue()
81 82
    }

83
    /// Removes elements from a set.
84
    ///
85 86 87 88 89 90
    /// See [https://redis.io/commands/srem](https://redis.io/commands/srem)
    /// - Parameters:
    ///     - elements: The values to remove from the set.
    ///     - key: The key of the set to remove from.
    /// - Returns: The number of elements that were removed from the set.
    @inlinable
91
    public func srem<Value: RESPValueConvertible>(_ elements: [Value], from key: String) -> EventLoopFuture<Int> {
92 93
        guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }

94 95 96 97
        var args: [RESPValue] = [.init(bulk: key)]
        args.append(convertingContentsOf: elements)
        
        return send(command: "SREM", with: args)
98
            .convertFromRESPValue()
99 100
    }

101
    /// Randomly selects and removes one or more elements in a set.
102
    ///
103 104 105 106 107 108 109 110 111 112
    /// See [https://redis.io/commands/spop](https://redis.io/commands/spop)
    /// - Parameters:
    ///     - key: The key of the set.
    ///     - count: The max number of elements to pop from the set.
    /// - Returns: The element that was popped from the set.
    @inlinable
    public func spop(from key: String, max count: Int = 1) -> EventLoopFuture<[RESPValue]> {
        assert(count >= 0, "A negative max count is nonsense.")

        guard count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
113 114 115 116 117 118
        
        let args: [RESPValue] = [
            .init(bulk: key),
            .init(bulk: count)
        ]
        return send(command: "SPOP", with: args)
119
            .convertFromRESPValue()
120 121
    }

122
    /// Randomly selects one or more elements in a set.
123 124 125 126 127
    ///
    ///     connection.srandmember("my_key") // pulls just one random element
    ///     connection.srandmember("my_key", max: -3) // pulls up to 3 elements, allowing duplicates
    ///     connection.srandmember("my_key", max: 3) // pulls up to 3 elements, guaranteed unique
    ///
128 129 130 131 132 133 134 135 136
    /// See [https://redis.io/commands/srandmember](https://redis.io/commands/srandmember)
    /// - Parameters:
    ///     - key: The key of the set.
    ///     - count: The max number of elements to select from the set.
    /// - Returns: The elements randomly selected from the set.
    @inlinable
    public func srandmember(from key: String, max count: Int = 1) -> EventLoopFuture<[RESPValue]> {
        guard count != 0 else { return self.eventLoop.makeSucceededFuture([]) }

137 138 139 140 141
        let args: [RESPValue] = [
            .init(bulk: key),
            .init(bulk: count)
        ]
        return send(command: "SRANDMEMBER", with: args)
142
            .convertFromRESPValue()
143 144
    }

145
    /// Moves an element from one set to another.
146
    ///
147 148 149 150 151 152 153
    /// See [https://redis.io/commands/smove](https://redis.io/commands/smove)
    /// - Parameters:
    ///     - element: The value to move from the source.
    ///     - sourceKey: The key of the source set.
    ///     - destKey: The key of the destination set.
    /// - Returns: `true` if the element was successfully removed from the source set.
    @inlinable
154 155
    public func smove<Value: RESPValueConvertible>(
        _ element: Value,
156 157 158 159 160
        from sourceKey: String,
        to destKey: String
    ) -> EventLoopFuture<Bool> {
        guard sourceKey != destKey else { return self.eventLoop.makeSucceededFuture(true) }

161 162 163 164 165 166
        let args: [RESPValue] = [
            .init(bulk: sourceKey),
            .init(bulk: destKey),
            element.convertedToRESPValue()
        ]
        return send(command: "SMOVE", with: args)
167
            .convertFromRESPValue()
168
            .map { return $0 == 1 }
169 170
    }

171
    /// Incrementally iterates over all values in a set.
172
    ///
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    /// See [https://redis.io/commands/sscan](https://redis.io/commands/sscan)
    /// - Parameters:
    ///     - key: The key of the set.
    ///     - position: The position to start the scan from.
    ///     - count: The number of elements to advance by. Redis default is 10.
    ///     - match: A glob-style pattern to filter values to be selected from the result set.
    /// - Returns: A cursor position for additional invocations with a limited collection of elements found in the set.
    @inlinable
    public func sscan(
        _ key: String,
        startingFrom position: Int = 0,
        count: Int? = nil,
        matching match: String? = nil
    ) -> EventLoopFuture<(Int, [RESPValue])> {
        return _scan(command: "SSCAN", key, position, count, match)
188
    }
189
}
190

191 192
// MARK: Diff

193
extension RedisClient {
194
    /// Calculates the difference between two or more sets.
195
    ///
196 197 198 199 200 201 202
    /// See [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff)
    /// - Parameter keys: The source sets to calculate the difference of.
    /// - Returns: A list of elements resulting from the difference.
    @inlinable
    public func sdiff(of keys: [String]) -> EventLoopFuture<[RESPValue]> {
        guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }

203 204
        let args = keys.map(RESPValue.init)
        return send(command: "SDIFF", with: args)
205
            .convertFromRESPValue()
206 207
    }

208 209
    /// Calculates the difference between two or more sets and stores the result.
    /// - Important: If the destination key already exists, it is overwritten.
210
    ///
211 212 213 214 215 216 217 218 219
    /// See [https://redis.io/commands/sdiffstore](https://redis.io/commands/sdiffstore)
    /// - Parameters:
    ///     - destination: The key of the new set from the result.
    ///     - sources: The list of source sets to calculate the difference of.
    /// - Returns: The number of elements in the difference result.
    @inlinable
    public func sdiffstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> {
        assert(keys.count > 0, "At least 1 key should be provided.")

220 221 222 223
        var args: [RESPValue] = [.init(bulk: destination)]
        args.append(convertingContentsOf: keys)
        
        return send(command: "SDIFFSTORE", with: args)
224
            .convertFromRESPValue()
225
    }
226
}
227

228 229
// MARK: Intersect

230
extension RedisClient {
231
    /// Calculates the intersection of two or more sets.
232
    ///
233 234 235 236 237 238 239
    /// See [https://redis.io/commands/sinter](https://redis.io/commands/sinter)
    /// - Parameter keys: The source sets to calculate the intersection of.
    /// - Returns: A list of elements resulting from the intersection.
    @inlinable
    public func sinter(of keys: [String]) -> EventLoopFuture<[RESPValue]> {
        guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }

240 241
        let args = keys.map(RESPValue.init)
        return send(command: "SINTER", with: args)
242
            .convertFromRESPValue()
243 244
    }

245 246
    /// Calculates the intersetion of two or more sets and stores the result.
    /// - Important: If the destination key already exists, it is overwritten.
247
    ///
248 249 250 251 252 253 254 255 256
    /// See [https://redis.io/commands/sinterstore](https://redis.io/commands/sinterstore)
    /// - Parameters:
    ///     - destination: The key of the new set from the result.
    ///     - sources: A list of source sets to calculate the intersection of.
    /// - Returns: The number of elements in the intersection result.
    @inlinable
    public func sinterstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> {
        assert(keys.count > 0, "At least 1 key should be provided.")

257 258 259 260
        var args: [RESPValue] = [.init(bulk: destination)]
        args.append(convertingContentsOf: keys)
        
        return send(command: "SINTERSTORE", with: args)
261
            .convertFromRESPValue()
262
    }
263
}
264

265 266
// MARK: Union

267
extension RedisClient {
268 269 270 271 272 273 274 275 276
    /// Calculates the union of two or more sets.
    ///
    /// See [https://redis.io/commands/sunion](https://redis.io/commands/sunion)
    /// - Parameter keys: The source sets to calculate the union of.
    /// - Returns: A list of elements resulting from the union.
    @inlinable
    public func sunion(of keys: [String]) -> EventLoopFuture<[RESPValue]> {
        guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
        
277 278
        let args = keys.map(RESPValue.init)
        return send(command: "SUNION", with: args)
279
            .convertFromRESPValue()
280 281
    }

282 283
    /// Calculates the union of two or more sets and stores the result.
    /// - Important: If the destination key already exists, it is overwritten.
284
    ///
285
    /// See [https://redis.io/commands/sunionstore](https://redis.io/commands/sunionstore)
286
    /// - Parameters:
287 288 289 290 291 292 293
    ///     - destination: The key of the new set from the result.
    ///     - sources: A list of source sets to calculate the union of.
    /// - Returns: The number of elements in the union result.
    @inlinable
    public func sunionstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> {
        assert(keys.count > 0, "At least 1 key should be provided.")

294 295 296 297
        var args: [RESPValue] = [.init(bulk: destination)]
        args.append(convertingContentsOf: keys)
        
        return send(command: "SUNIONSTORE", with: args)
298
            .convertFromRESPValue()
299 300
    }
}