Skip to content

Commit

Permalink
Improve error reporting for connection closure, invalid query syntax,…
Browse files Browse the repository at this point in the history
… and unique constraint violations. (#68)
  • Loading branch information
gwynne authored Dec 6, 2021
1 parent 03cc5d4 commit f0e8ad7
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 20 deletions.
5 changes: 5 additions & 0 deletions Sources/MySQLNIO/MySQLConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ public final class MySQLConnection: MySQLDatabase {
}

public func send(_ command: MySQLCommand, logger: Logger) -> EventLoopFuture<Void> {
guard self.channel.isActive else {
return self.channel.eventLoop.makeFailedFuture(MySQLError.closed)
}

let promise = self.eventLoop.makePromise(of: Void.self)

let c = MySQLCommandContext(
handler: command,
promise: promise
Expand Down
5 changes: 5 additions & 0 deletions Sources/MySQLNIO/MySQLError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public enum MySQLError: Error, CustomStringConvertible, LocalizedError {
/// A uniqueness constraint was violated. Associated value is message from server with details.
case duplicateEntry(String)

/// A syntax error occurred in a query. Associated value is message from server with details.
case invalidSyntax(String)

public var message: String {
switch self {
case .secureConnectionRequired:
Expand All @@ -39,6 +42,8 @@ public enum MySQLError: Error, CustomStringConvertible, LocalizedError {
return "Connection closed."
case .duplicateEntry(let message):
return "Duplicate entry: \(message)"
case .invalidSyntax(let message):
return "Invalid syntax: \(message)"
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/MySQLNIO/MySQLQueryCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ private final class MySQLQueryCommand: MySQLCommand {
switch errorPacket.errorCode {
case .DUP_ENTRY:
error = MySQLError.duplicateEntry(errorPacket.errorMessage)
case .PARSE_ERROR:
error = MySQLError.invalidSyntax(errorPacket.errorMessage)
default:
error = MySQLError.server(errorPacket)
}
Expand Down
13 changes: 11 additions & 2 deletions Sources/MySQLNIO/MySQLSimpleQueryCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,17 @@ private final class MySQLSimpleQueryCommand: MySQLCommand {
// print("QUERY \(state): \(packet.payload.debugDescription)")
guard !packet.isError else {
self.state = .done
let error = try packet.decode(MySQLProtocol.ERR_Packet.self, capabilities: capabilities)
throw MySQLError.server(error)
let errorPacket = try packet.decode(MySQLProtocol.ERR_Packet.self, capabilities: capabilities)
let error: Error
switch errorPacket.errorCode {
case .DUP_ENTRY:
error = MySQLError.duplicateEntry(errorPacket.errorMessage)
case .PARSE_ERROR:
error = MySQLError.invalidSyntax(errorPacket.errorMessage)
default:
error = MySQLError.server(errorPacket)
}
throw error
}
switch self.state {
case .ready:
Expand Down
40 changes: 22 additions & 18 deletions Tests/MySQLNIOTests/MySQLNIOTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,35 +108,39 @@ final class MySQLNIOTests: XCTestCase {
func testSimpleQuery_syntaxError() throws {
let conn = try MySQLConnection.test(on: self.eventLoop).wait()
defer { try! conn.close().wait() }
do {
_ = try conn.simpleQuery("SELECT &").wait()
XCTFail("should have thrown")
} catch let error as MySQLError {
switch error {
case .server(let packet):
XCTAssertEqual(packet.errorCode, .PARSE_ERROR)
default:
XCTFail("unexpected error type")
XCTAssertThrowsError(try conn.simpleQuery("SELECT &").wait()) { error in
guard case .invalidSyntax = error as? MySQLError else {
return XCTFail("Exected MySQLError.invalidSyntax, but found \(error)")
}
}
}

func testQuery_syntaxError() throws {
let conn = try MySQLConnection.test(on: self.eventLoop).wait()
defer { try! conn.close().wait() }
do {
_ = try conn.query("SELECT &").wait()
XCTFail("should have thrown")
} catch let error as MySQLError {
switch error {
case .server(let packet):
XCTAssertEqual(packet.errorCode, .PARSE_ERROR)
default:
XCTFail("unexpected error type")
XCTAssertThrowsError(try conn.query("SELECT &").wait()) { error in
guard case .invalidSyntax = error as? MySQLError else {
return XCTFail("Exected MySQLError.invalidSyntax, but found \(error)")
}
}
}

func testSimpleQuery_duplicateEntry() throws {
let conn = try MySQLConnection.test(on: self.eventLoop).wait()
defer { try! conn.close().wait() }
let dropResults = try conn.simpleQuery("DROP TABLE IF EXISTS foos").wait()
XCTAssertEqual(dropResults.count, 0)
let createResults = try conn.simpleQuery("CREATE TABLE foos (id BIGINT SIGNED unique, name VARCHAR(64))").wait()
XCTAssertEqual(createResults.count, 0)
let insertResults = try conn.simpleQuery("INSERT INTO foos VALUES (1, 'one')").wait()
XCTAssertEqual(insertResults.count, 0)
XCTAssertThrowsError(try conn.query("INSERT INTO foos VALUES (1, 'two')").wait()) { error in
guard case .duplicateEntry = error as? MySQLError else {
return XCTFail("Expected MySQLError.duplicateEntry, but found \(error)")
}
}
}

func testQuery_duplicateEntry() throws {
let conn = try MySQLConnection.test(on: self.eventLoop).wait()
defer { try! conn.close().wait() }
Expand Down

0 comments on commit f0e8ad7

Please sign in to comment.