diff --git a/Sources/MySQLNIO/MySQLConnection.swift b/Sources/MySQLNIO/MySQLConnection.swift index c25f74a..04f0f36 100644 --- a/Sources/MySQLNIO/MySQLConnection.swift +++ b/Sources/MySQLNIO/MySQLConnection.swift @@ -71,7 +71,12 @@ public final class MySQLConnection: MySQLDatabase { } public func send(_ command: MySQLCommand, logger: Logger) -> EventLoopFuture { + 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 diff --git a/Sources/MySQLNIO/MySQLError.swift b/Sources/MySQLNIO/MySQLError.swift index cbcb1f5..f254bc8 100644 --- a/Sources/MySQLNIO/MySQLError.swift +++ b/Sources/MySQLNIO/MySQLError.swift @@ -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: @@ -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)" } } diff --git a/Sources/MySQLNIO/MySQLQueryCommand.swift b/Sources/MySQLNIO/MySQLQueryCommand.swift index 3c1c10d..2515eb6 100644 --- a/Sources/MySQLNIO/MySQLQueryCommand.swift +++ b/Sources/MySQLNIO/MySQLQueryCommand.swift @@ -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) } diff --git a/Sources/MySQLNIO/MySQLSimpleQueryCommand.swift b/Sources/MySQLNIO/MySQLSimpleQueryCommand.swift index 6fe9717..462234a 100644 --- a/Sources/MySQLNIO/MySQLSimpleQueryCommand.swift +++ b/Sources/MySQLNIO/MySQLSimpleQueryCommand.swift @@ -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: diff --git a/Tests/MySQLNIOTests/MySQLNIOTests.swift b/Tests/MySQLNIOTests/MySQLNIOTests.swift index bea091d..6b24549 100644 --- a/Tests/MySQLNIOTests/MySQLNIOTests.swift +++ b/Tests/MySQLNIOTests/MySQLNIOTests.swift @@ -108,15 +108,9 @@ 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)") } } } @@ -124,19 +118,29 @@ final class MySQLNIOTests: XCTestCase { 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() }