SetCommands.swift 21.2 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

Nathan Harris's avatar
Nathan Harris committed
17
18
// MARK: General

19
extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
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.
    ///
Nathan Harris's avatar
Nathan Harris committed
25
26
27
    /// 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.
28
    public func smembers(of key: RedisKey) -> EventLoopFuture<[RESPValue]> {
29
        let args = [RESPValue(from: key)]
30
        return send(command: "SMEMBERS", with: args)
31
            .tryConverting()
32
    }
Nathan Harris's avatar
Nathan Harris committed
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    
    /// 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.
    ///
    /// Results are **UNSTABLE** in regards to the ordering of insertions through the `sadd` command and this method.
    ///
    /// See [https://redis.io/commands/smembers](https://redis.io/commands/smembers)
    /// - Parameters:
    ///     - key: The key of the set.
    ///     - type: The type to convert the values to.
    /// - Returns: A list of elements found within the set. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func smembers<Value: RESPValueConvertible>(of key: RedisKey, as type: Value.Type) -> EventLoopFuture<[Value?]> {
        return self.smembers(of: key)
            .map { return $0.map(Value.init(fromRESP:)) }
    }
49

Nathan Harris's avatar
Nathan Harris committed
50
    /// Checks if the element is included in a set.
51
    ///
Nathan Harris's avatar
Nathan Harris committed
52
53
54
55
56
57
    /// 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
58
    public func sismember<Value: RESPValueConvertible>(_ element: Value, of key: RedisKey) -> EventLoopFuture<Bool> {
59
        let args: [RESPValue] = [
60
            .init(from: key),
61
62
63
            element.convertedToRESPValue()
        ]
        return send(command: "SISMEMBER", with: args)
64
            .tryConverting(to: Int.self)
65
            .map { return $0 == 1 }
66
67
    }

Nathan Harris's avatar
Nathan Harris committed
68
    /// Gets the total count of elements within a set.
69
    ///
Nathan Harris's avatar
Nathan Harris committed
70
71
72
    /// 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.
73
    public func scard(of key: RedisKey) -> EventLoopFuture<Int> {
74
        let args = [RESPValue(from: key)]
75
        return send(command: "SCARD", with: args)
76
            .tryConverting()
77
78
    }

Nathan Harris's avatar
Nathan Harris committed
79
    /// Adds elements to a set.
80
    ///
Nathan Harris's avatar
Nathan Harris committed
81
82
83
84
85
86
    /// 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
87
    public func sadd<Value: RESPValueConvertible>(_ elements: [Value], to key: RedisKey) -> EventLoopFuture<Int> {
Nathan Harris's avatar
Nathan Harris committed
88
        guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
89
        
90
        var args: [RESPValue] = [.init(from: key)]
91
        args.append(convertingContentsOf: elements)
Nathan Harris's avatar
Nathan Harris committed
92

93
        return send(command: "SADD", with: args)
94
            .tryConverting()
95
    }
96
97
98
99
100
101
102
103
104
    
    /// Adds elements to a set.
    ///
    /// 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
105
    public func sadd<Value: RESPValueConvertible>(_ elements: Value..., to key: RedisKey) -> EventLoopFuture<Int> {
106
107
        return self.sadd(elements, to: key)
    }
108

Nathan Harris's avatar
Nathan Harris committed
109
    /// Removes elements from a set.
110
    ///
Nathan Harris's avatar
Nathan Harris committed
111
112
113
114
115
116
    /// 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
117
    public func srem<Value: RESPValueConvertible>(_ elements: [Value], from key: RedisKey) -> EventLoopFuture<Int> {
Nathan Harris's avatar
Nathan Harris committed
118
119
        guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }

120
        var args: [RESPValue] = [.init(from: key)]
121
122
123
        args.append(convertingContentsOf: elements)
        
        return send(command: "SREM", with: args)
124
            .tryConverting()
125
    }
126
127
128
129
130
131
132
133
134
    
    /// Removes elements from a set.
    ///
    /// 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
135
    public func srem<Value: RESPValueConvertible>(_ elements: Value..., from key: RedisKey) -> EventLoopFuture<Int> {
136
137
        return self.srem(elements, from: key)
    }
138

Nathan Harris's avatar
Nathan Harris committed
139
    /// Randomly selects and removes one or more elements in a set.
140
    ///
Nathan Harris's avatar
Nathan Harris committed
141
142
143
144
145
    /// 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.
146
    public func spop(from key: RedisKey, max count: Int = 1) -> EventLoopFuture<[RESPValue]> {
Nathan Harris's avatar
Nathan Harris committed
147
148
149
        assert(count >= 0, "A negative max count is nonsense.")

        guard count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
150
151
        
        let args: [RESPValue] = [
152
            .init(from: key),
153
154
155
            .init(bulk: count)
        ]
        return send(command: "SPOP", with: args)
156
            .tryConverting()
157
158
    }

Nathan Harris's avatar
Nathan Harris committed
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
    /// Randomly selects and removes one or more elements in a set.
    ///
    /// See [https://redis.io/commands/spop](https://redis.io/commands/spop)
    /// - Parameters:
    ///     - key: The key of the set.
    ///     - type: The type to convert the values to.
    ///     - count: The max number of elements to pop from the set.
    /// - Returns: The element that was popped from the set. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func spop<Value: RESPValueConvertible>(
        from key: RedisKey,
        as type: Value.Type,
        max count: Int = 1
    ) -> EventLoopFuture<[Value?]> {
        return self.spop(from: key, max: count)
            .map { return $0.map(Value.init(fromRESP:)) }
    }

Nathan Harris's avatar
Nathan Harris committed
177
    /// Randomly selects one or more elements in a set.
178
179
180
181
182
    ///
    ///     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
    ///
Nathan Harris's avatar
Nathan Harris committed
183
184
185
186
187
    /// 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.
188
    public func srandmember(from key: RedisKey, max count: Int = 1) -> EventLoopFuture<[RESPValue]> {
Nathan Harris's avatar
Nathan Harris committed
189
190
        guard count != 0 else { return self.eventLoop.makeSucceededFuture([]) }

191
        let args: [RESPValue] = [
192
            .init(from: key),
193
194
195
            .init(bulk: count)
        ]
        return send(command: "SRANDMEMBER", with: args)
196
            .tryConverting()
197
    }
Nathan Harris's avatar
Nathan Harris committed
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
    
    /// Randomly selects one or more elements in a set.
    ///
    /// See [https://redis.io/commands/srandmember](https://redis.io/commands/srandmember)
    /// - Parameters:
    ///     - key: The key of the set.
    ///     - type; The type to convert the values to.
    ///     - count: The max number of elements to select from the set.
    /// - Returns: The elements randomly selected from the set. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func srandmember<Value: RESPValueConvertible>(
        from key: RedisKey,
        as type: Value.Type,
        max count: Int = 1
    ) -> EventLoopFuture<[Value?]> {
        return self.srandmember(from: key, max: count)
            .map { return $0.map(Value.init(fromRESP:)) }
    }
216

Nathan Harris's avatar
Nathan Harris committed
217
    /// Moves an element from one set to another.
218
    ///
Nathan Harris's avatar
Nathan Harris committed
219
220
221
222
223
224
225
    /// 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
226
227
    public func smove<Value: RESPValueConvertible>(
        _ element: Value,
228
229
        from sourceKey: RedisKey,
        to destKey: RedisKey
Nathan Harris's avatar
Nathan Harris committed
230
231
232
    ) -> EventLoopFuture<Bool> {
        guard sourceKey != destKey else { return self.eventLoop.makeSucceededFuture(true) }

233
        let args: [RESPValue] = [
234
235
            .init(from: sourceKey),
            .init(from: destKey),
236
237
238
            element.convertedToRESPValue()
        ]
        return send(command: "SMOVE", with: args)
239
            .tryConverting()
Nathan Harris's avatar
Nathan Harris committed
240
            .map { return $0 == 1 }
241
242
    }

Nathan Harris's avatar
Nathan Harris committed
243
    /// Incrementally iterates over all values in a set.
244
    ///
Nathan Harris's avatar
Nathan Harris committed
245
246
247
248
249
250
251
252
    /// 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.
    public func sscan(
253
        _ key: RedisKey,
Nathan Harris's avatar
Nathan Harris committed
254
        startingFrom position: Int = 0,
Nathan Harris's avatar
Nathan Harris committed
255
256
        matching match: String? = nil,
        count: Int? = nil
Nathan Harris's avatar
Nathan Harris committed
257
    ) -> EventLoopFuture<(Int, [RESPValue])> {
Nathan Harris's avatar
Nathan Harris committed
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
        return _scan(command: "SSCAN", key, position, match, count)
    }
    
    /// Incrementally iterates over all values in a set.
    ///
    /// 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.
    ///     - 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 value to.
    /// - Returns: A cursor position for additional invocations with a limited collection of elements found in the set. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func sscan<Value: RESPValueConvertible>(
        _ key: RedisKey,
        startingFrom position: Int = 0,
        matching match: String? = nil,
        count: Int? = nil,
        valueType: Value.Type
    ) -> EventLoopFuture<(Int, [Value?])> {
        return self.sscan(key, startingFrom: position, matching: match, count: count)
            .map { (cursor, rawValues) in
                let values = rawValues.map(Value.init(fromRESP:))
                return (cursor, values)
            }
284
    }
Nathan Harris's avatar
Nathan Harris committed
285
}
286

Nathan Harris's avatar
Nathan Harris committed
287
288
// MARK: Diff

289
extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
290
    /// Calculates the difference between two or more sets.
291
    ///
Nathan Harris's avatar
Nathan Harris committed
292
293
294
    /// 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.
295
    public func sdiff(of keys: [RedisKey]) -> EventLoopFuture<[RESPValue]> {
Nathan Harris's avatar
Nathan Harris committed
296
297
        guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }

298
299
        let args = keys.map(RESPValue.init)
        return send(command: "SDIFF", with: args)
300
            .tryConverting()
301
    }
302
    
Nathan Harris's avatar
Nathan Harris committed
303
304
305
306
307
308
309
310
311
312
313
314
315
    /// Calculates the difference between two or more sets.
    ///
    /// See [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff)
    /// - Parameters:
    ///     - keys: The source sets to calculate the difference of.
    ///     - valueType: The type to convert the values to.
    /// - Returns: A list of elements resulting from the difference. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func sdiff<Value: RESPValueConvertible>(of keys: [RedisKey], valueType: Value.Type) -> EventLoopFuture<[Value?]> {
        return self.sdiff(of: keys)
            .map { return $0.map(Value.init(fromRESP:)) }
    }
    
316
317
318
319
320
    /// Calculates the difference between two or more sets.
    ///
    /// 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.
321
    public func sdiff(of keys: RedisKey...) -> EventLoopFuture<[RESPValue]> {
322
323
        return self.sdiff(of: keys)
    }
Nathan Harris's avatar
Nathan Harris committed
324
325
326
327
328
329
330
331
332
333
334
335
    
    /// Calculates the difference between two or more sets.
    ///
    /// See [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff)
    /// - Parameters:
    ///     - keys: The source sets to calculate the difference of.
    ///     - valueType: The type to convert the values to.
    /// - Returns: A list of elements resulting from the difference. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func sdiff<Value: RESPValueConvertible>(of keys: RedisKey..., valueType: Value.Type) -> EventLoopFuture<[Value?]> {
        return self.sdiff(of: keys, valueType: valueType)
    }
336

Nathan Harris's avatar
Nathan Harris committed
337
338
    /// Calculates the difference between two or more sets and stores the result.
    /// - Important: If the destination key already exists, it is overwritten.
339
    ///
Nathan Harris's avatar
Nathan Harris committed
340
341
342
343
344
    /// 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.
345
    public func sdiffstore(as destination: RedisKey, sources keys: [RedisKey]) -> EventLoopFuture<Int> {
Nathan Harris's avatar
Nathan Harris committed
346
347
        assert(keys.count > 0, "At least 1 key should be provided.")

348
        var args: [RESPValue] = [.init(from: destination)]
349
350
351
        args.append(convertingContentsOf: keys)
        
        return send(command: "SDIFFSTORE", with: args)
352
            .tryConverting()
353
    }
Nathan Harris's avatar
Nathan Harris committed
354
}
355

Nathan Harris's avatar
Nathan Harris committed
356
357
// MARK: Intersect

358
extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
359
    /// Calculates the intersection of two or more sets.
360
    ///
Nathan Harris's avatar
Nathan Harris committed
361
362
363
    /// 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.
364
    public func sinter(of keys: [RedisKey]) -> EventLoopFuture<[RESPValue]> {
Nathan Harris's avatar
Nathan Harris committed
365
366
        guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }

367
368
        let args = keys.map(RESPValue.init)
        return send(command: "SINTER", with: args)
369
            .tryConverting()
370
    }
371
    
Nathan Harris's avatar
Nathan Harris committed
372
373
374
375
376
377
378
379
380
381
382
383
384
    /// Calculates the intersection of two or more sets.
    ///
    /// See [https://redis.io/commands/sinter](https://redis.io/commands/sinter)
    /// - Parameters:
    ///     - keys: The source sets to calculate the intersection of.
    ///     - valueType: The type to convert all values to.
    /// - Returns: A list of elements resulting from the intersection. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func sinter<Value: RESPValueConvertible>(of keys: [RedisKey], valueType: Value.Type) -> EventLoopFuture<[Value?]> {
        return self.sinter(of: keys)
            .map { return $0.map(Value.init(fromRESP:)) }
    }
    
385
386
387
388
389
    /// Calculates the intersection of two or more sets.
    ///
    /// 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.
390
    public func sinter(of keys: RedisKey...) -> EventLoopFuture<[RESPValue]> {
391
392
        return self.sinter(of: keys)
    }
Nathan Harris's avatar
Nathan Harris committed
393
394
395
396
397
398
399
400
401
402
403
404
    
    /// Calculates the intersection of two or more sets.
    ///
    /// See [https://redis.io/commands/sinter](https://redis.io/commands/sinter)
    /// - Parameters:
    ///     - keys: The source sets to calculate the intersection of.
    ///     - valueType: The type to convert all values to.
    /// - Returns: A list of elements resulting from the intersection. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func sinter<Value: RESPValueConvertible>(of keys: RedisKey..., valueType: Value.Type) -> EventLoopFuture<[Value?]> {
        return self.sinter(of: keys, valueType: valueType)
    }
405

Nathan Harris's avatar
Nathan Harris committed
406
407
    /// Calculates the intersetion of two or more sets and stores the result.
    /// - Important: If the destination key already exists, it is overwritten.
408
    ///
Nathan Harris's avatar
Nathan Harris committed
409
410
411
412
413
    /// 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.
414
    public func sinterstore(as destination: RedisKey, sources keys: [RedisKey]) -> EventLoopFuture<Int> {
Nathan Harris's avatar
Nathan Harris committed
415
416
        assert(keys.count > 0, "At least 1 key should be provided.")

417
        var args: [RESPValue] = [.init(from: destination)]
418
419
420
        args.append(convertingContentsOf: keys)
        
        return send(command: "SINTERSTORE", with: args)
421
            .tryConverting()
422
    }
Nathan Harris's avatar
Nathan Harris committed
423
}
424

Nathan Harris's avatar
Nathan Harris committed
425
426
// MARK: Union

427
extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
428
429
430
431
432
    /// 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.
433
    public func sunion(of keys: [RedisKey]) -> EventLoopFuture<[RESPValue]> {
Nathan Harris's avatar
Nathan Harris committed
434
435
        guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
        
436
437
        let args = keys.map(RESPValue.init)
        return send(command: "SUNION", with: args)
438
            .tryConverting()
439
    }
440
    
Nathan Harris's avatar
Nathan Harris committed
441
442
443
444
445
446
447
448
449
450
451
452
453
    /// Calculates the union of two or more sets.
    ///
    /// See [https://redis.io/commands/sunion](https://redis.io/commands/sunion)
    /// - Parameters:
    ///     - keys: The source sets to calculate the union of.
    ///     - valueType: The type to convert all values to.
    /// - Returns: A list of elements resulting from the union. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func sunion<Value: RESPValueConvertible>(of keys: [RedisKey], valueType: Value.Type) -> EventLoopFuture<[Value?]> {
        return self.sunion(of: keys)
            .map { return $0.map(Value.init(fromRESP:)) }
    }
    
454
455
456
457
458
    /// 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.
459
    public func sunion(of keys: RedisKey...) -> EventLoopFuture<[RESPValue]> {
460
461
        return self.sunion(of: keys)
    }
Nathan Harris's avatar
Nathan Harris committed
462
463
464
465
466
467
468
469
470
471
472
473
    
    /// Calculates the union of two or more sets.
    ///
    /// See [https://redis.io/commands/sunion](https://redis.io/commands/sunion)
    /// - Parameters:
    ///     - keys: The source sets to calculate the union of.
    ///     - valueType: The type to convert all values to.
    /// - Returns: A list of elements resulting from the union. Elements that fail the `RESPValue` conversion will be `nil`.
    @inlinable
    public func sunion<Value: RESPValueConvertible>(of keys: RedisKey..., valueType: Value.Type) -> EventLoopFuture<[Value?]> {
        return self.sunion(of: keys, valueType: valueType)
    }
474

Nathan Harris's avatar
Nathan Harris committed
475
476
    /// Calculates the union of two or more sets and stores the result.
    /// - Important: If the destination key already exists, it is overwritten.
477
    ///
Nathan Harris's avatar
Nathan Harris committed
478
    /// See [https://redis.io/commands/sunionstore](https://redis.io/commands/sunionstore)
479
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
480
481
482
    ///     - 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.
483
    public func sunionstore(as destination: RedisKey, sources keys: [RedisKey]) -> EventLoopFuture<Int> {
Nathan Harris's avatar
Nathan Harris committed
484
485
        assert(keys.count > 0, "At least 1 key should be provided.")

486
        var args: [RESPValue] = [.init(from: destination)]
487
488
489
        args.append(convertingContentsOf: keys)
        
        return send(command: "SUNIONSTORE", with: args)
490
            .tryConverting()
491
492
    }
}