Skip to content

Implement Delete Operation Support #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/SwiftMemcache/Extensions/UInt8+Characters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extension UInt8 {
static var m: UInt8 = .init(ascii: "m")
static var s: UInt8 = .init(ascii: "s")
static var g: UInt8 = .init(ascii: "g")
static var d: UInt8 = .init(ascii: "d")
static var v: UInt8 = .init(ascii: "v")
static var T: UInt8 = .init(ascii: "T")
static var zero: UInt8 = .init(ascii: "0")
Expand Down
33 changes: 33 additions & 0 deletions Sources/SwiftMemcache/MemcachedConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public actor MemcachedConnection {
case connectionShutdown
/// Indicates that a nil response was received from the server.
case unexpectedNilResponse
/// Indicates that the key was not found.
case keyNotFound
}

private var state: State
Expand Down Expand Up @@ -247,4 +249,35 @@ public actor MemcachedConnection {
throw MemcachedConnectionError.connectionShutdown
}
}

// MARK: - Deleting a Value

/// Delete the value for a key from the Memcache server.
///
/// - Parameter key: The key of the item to be deleted.
/// - Throws: A `MemcachedConnectionError.connectionShutdown` error if the connection to the Memcache server is shut down.
/// - Throws: A `MemcachedConnectionError.unexpectedNilResponse` error if the key was not found or if an unexpected response code was returned.
public func delete(_ key: String) async throws {
switch self.state {
case .initial(_, _, _, _),
.running:

let command = MemcachedRequest.DeleteCommand(key: key)
let request = MemcachedRequest.delete(command)

let response = try await sendRequest(request)

switch response.returnCode {
case .HD:
return
case .NF:
throw MemcachedConnectionError.keyNotFound
default:
throw MemcachedConnectionError.unexpectedNilResponse
}

case .finished:
throw MemcachedConnectionError.connectionShutdown
}
}
}
5 changes: 5 additions & 0 deletions Sources/SwiftMemcache/MemcachedRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ enum MemcachedRequest {
var flags: MemcachedFlags
}

struct DeleteCommand {
let key: String
}

case set(SetCommand)
case get(GetCommand)
case delete(DeleteCommand)
}
13 changes: 13 additions & 0 deletions Sources/SwiftMemcache/MemcachedRequestEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ struct MemcachedRequestEncoder: MessageToByteEncoder {
// write flags if there are any
out.writeMemcachedFlags(flags: command.flags)

// write separator
out.writeInteger(UInt8.carriageReturn)
out.writeInteger(UInt8.newline)

case .delete(let command):
precondition(!command.key.isEmpty, "Key must not be empty")

// write command and key
out.writeInteger(UInt8.m)
out.writeInteger(UInt8.d)
out.writeInteger(UInt8.whitespace)
out.writeBytes(command.key.utf8)

// write separator
out.writeInteger(UInt8.carriageReturn)
out.writeInteger(UInt8.newline)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,30 @@ final class MemcachedIntegrationTest: XCTestCase {
}
}

func testDeleteValue() async throws {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try! group.syncShutdownGracefully())
}
let memcachedConnection = MemcachedConnection(host: "memcached", port: 11211, eventLoopGroup: group)

try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { try await memcachedConnection.run() }

// Set key and value
let setValue = "foo"
try await memcachedConnection.set("bar", value: setValue)

// Delete the key
do {
try await memcachedConnection.delete("bar")
} catch {
XCTFail("Deletion attempt should be successful, but threw: \(error)")
}
group.cancelAll()
}
}

func testMemcachedConnectionWithUInt() async throws {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,21 @@ final class MemcachedRequestEncoderTests: XCTestCase {
let expectedEncodedData = "mg foo v\r\n"
XCTAssertEqual(outBuffer.getString(at: 0, length: outBuffer.readableBytes), expectedEncodedData)
}

func testEncodeDeleteRequest() {
// Prepare a MemcachedRequest
let command = MemcachedRequest.DeleteCommand(key: "foo")
let request = MemcachedRequest.delete(command)

// Pass our request through the encoder
var outBuffer = ByteBufferAllocator().buffer(capacity: 0)
do {
try self.encoder.encode(data: request, out: &outBuffer)
} catch {
XCTFail("Encoding failed with error: \(error)")
}

let expectedEncodedData = "md foo\r\n"
XCTAssertEqual(outBuffer.getString(at: 0, length: outBuffer.readableBytes), expectedEncodedData)
}
}