Skip to content

Commit e7b451c

Browse files
committed
Add SETEX and PSETEX commands
Motivation: The SETEX and PSETEX commands are missing. Modifications: - Add SETEX command - Add PSETEX command - Add integration tests Result: Users can atomically set a key with an expire
1 parent ddfc7b0 commit e7b451c

File tree

3 files changed

+80
-0
lines changed

3 files changed

+80
-0
lines changed

Sources/RediStack/Commands/StringCommands.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,60 @@ extension RedisClient {
293293
.map { $0 == 1 }
294294
}
295295

296+
/// Sets a key to the provided value and an expiration timeout in seconds.
297+
///
298+
/// See [https://redis.io/commands/setex](https://redis.io/commands/setex)
299+
/// - Important: Regardless of the type of data stored at the key, it will be overwritten to a "string" data type.
300+
///
301+
/// ie. If the key is a reference to a Sorted Set, its value will be overwritten to be a "string" data type.
302+
/// - Important: The actual expiration used will be the specified value or `1`, whichever is larger.
303+
/// - Parameters:
304+
/// - key: The key to use to uniquely identify this value.
305+
/// - value: The value to set the key to.
306+
/// - expiration: The number of seconds after which to expire the key.
307+
/// - Returns: A `NIO.EventLoopFuture` that resolves if the operation was successful.
308+
@inlinable
309+
public func setex<Value: RESPValueConvertible>(
310+
_ key: RedisKey,
311+
to value: Value,
312+
expirationInSeconds expiration: Int
313+
) -> EventLoopFuture<Void> {
314+
let args: [RESPValue] = [
315+
.init(from: key),
316+
.init(from: max(1, expiration)),
317+
value.convertedToRESPValue()
318+
]
319+
return self.send(command: "SETEX", with: args)
320+
.map { _ in () }
321+
}
322+
323+
/// Sets a key to the provided value and an expiration timeout in milliseconds.
324+
///
325+
/// See [https://redis.io/commands/psetex](https://redis.io/commands/psetex)
326+
/// - Important: Regardless of the type of data stored at the key, it will be overwritten to a "string" data type.
327+
///
328+
/// ie. If the key is a reference to a Sorted Set, its value will be overwritten to be a "string" data type.
329+
/// - Important: The actual expiration used will be the specified value or `1`, whichever is larger.
330+
/// - Parameters:
331+
/// - key: The key to use to uniquely identify this value.
332+
/// - value: The value to set the key to.
333+
/// - expiration: The number of milliseconds after which to expire the key.
334+
/// - Returns: A `NIO.EventLoopFuture` that resolves if the operation was successful.
335+
@inlinable
336+
public func psetex<Value: RESPValueConvertible>(
337+
_ key: RedisKey,
338+
to value: Value,
339+
expirationInMilliseconds expiration: Int
340+
) -> EventLoopFuture<Void> {
341+
let args: [RESPValue] = [
342+
.init(from: key),
343+
.init(from: max(1, expiration)),
344+
value.convertedToRESPValue()
345+
]
346+
return self.send(command: "PSETEX", with: args)
347+
.map { _ in () }
348+
}
349+
296350
/// Sets each key to their respective new value, overwriting existing values.
297351
/// - Note: Use `msetnx(_:)` if you don't want to overwrite values.
298352
///

Tests/RediStackIntegrationTests/Commands/StringCommandsTests.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,30 @@ final class StringCommandsTests: RediStackIntegrationTestCase {
121121
XCTAssertFalse(try connection.setnx(#function, to: "value").wait())
122122
}
123123

124+
func test_setex() throws {
125+
XCTAssertNoThrow(try connection.setex(#function, to: "value", expirationInSeconds: 42).wait())
126+
let ttl = try connection.ttl(#function).wait()
127+
switch ttl {
128+
case .keyDoesNotExist, .unlimited:
129+
XCTFail("Unexpected TTL for \(#function)")
130+
case .limited(let lifetime):
131+
XCTAssertGreaterThan(lifetime.timeAmount, .nanoseconds(0))
132+
XCTAssertLessThanOrEqual(lifetime.timeAmount, .seconds(42))
133+
}
134+
}
135+
136+
func test_psetex() throws {
137+
XCTAssertNoThrow(try connection.psetex(#function, to: "value", expirationInMilliseconds: 42_000).wait())
138+
let ttl = try connection.pttl(#function).wait()
139+
switch ttl {
140+
case .keyDoesNotExist, .unlimited:
141+
XCTFail("Unexpected TTL for \(#function)")
142+
case .limited(let lifetime):
143+
XCTAssertGreaterThan(lifetime.timeAmount, .nanoseconds(0))
144+
XCTAssertLessThanOrEqual(lifetime.timeAmount, .milliseconds(42_000))
145+
}
146+
}
147+
124148
func test_append() throws {
125149
let result = "value appended"
126150
XCTAssertNoThrow(try connection.append("value", to: #function).wait())

Tests/RediStackIntegrationTests/XCTestManifests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,12 @@ extension StringCommandsTests {
157157
("test_mget", test_mget),
158158
("test_mset", test_mset),
159159
("test_msetnx", test_msetnx),
160+
("test_psetex", test_psetex),
160161
("test_set_condition_and_expiration", test_set_condition_and_expiration),
161162
("test_set_condition", test_set_condition),
162163
("test_set_expiration", test_set_expiration),
163164
("test_set", test_set),
165+
("test_setex", test_setex),
164166
("test_setnx", test_setnx),
165167
]
166168
}

0 commit comments

Comments
 (0)