From b32db1edb688d1edbcee5439f8e74dedfd8d7406 Mon Sep 17 00:00:00 2001 From: "Stuart A. Malone" Date: Sun, 20 Feb 2022 07:12:25 -0500 Subject: [PATCH 1/4] Parse timestamp columns in text format. Fixes vapor/mysql-nio#71 Signed-off-by: Stuart A. Malone --- Sources/MySQLNIO/MySQLData.swift | 29 +++++++++++++++++++++---- Tests/MySQLNIOTests/MySQLNIOTests.swift | 13 +++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Sources/MySQLNIO/MySQLData.swift b/Sources/MySQLNIO/MySQLData.swift index d7383a4..b05fb5e 100644 --- a/Sources/MySQLNIO/MySQLData.swift +++ b/Sources/MySQLNIO/MySQLData.swift @@ -434,10 +434,31 @@ public struct MySQLData: CustomStringConvertible, ExpressibleByStringLiteral, Ex guard var buffer = self.buffer else { return nil } - switch self.type { - case .timestamp, .datetime, .date, .time: - return buffer.readMySQLTime() - default: return nil + switch self.format { + case .binary: + switch self.type { + case .timestamp, .datetime, .date, .time: + return buffer.readMySQLTime() + default: return nil + } + case .text: + if let s = buffer.readString(length: buffer.readableBytes) { + var ctime = tm() + let format = "%Y-%m-%d %H:%M:%S" + _ = format.withCString { cf in + s.withCString { cs in + strptime(cs, cf, &ctime) + } + } + return MySQLTime(year: UInt16(ctime.tm_year + 1900), + month: UInt16(ctime.tm_mon + 1), + day: UInt16(ctime.tm_mday), + hour: UInt16(ctime.tm_hour), + minute: UInt16(ctime.tm_min), + second: UInt16(ctime.tm_sec), + microsecond: nil) + } + return nil } } diff --git a/Tests/MySQLNIOTests/MySQLNIOTests.swift b/Tests/MySQLNIOTests/MySQLNIOTests.swift index 6b24549..fef56b1 100644 --- a/Tests/MySQLNIOTests/MySQLNIOTests.swift +++ b/Tests/MySQLNIOTests/MySQLNIOTests.swift @@ -636,6 +636,19 @@ final class MySQLNIOTests: XCTestCase { XCTAssertEqual(time.date, nil) } + func testTextMySQLTimeParse() throws { + let conn = try MySQLConnection.test(on: self.eventLoop).wait() + defer { try! conn.close().wait() } + + // MariaDB 10.5 returns timestamp columns in text format. + // Ensure these can be converted to MySQLTime without error. + let rows = try! conn.simpleQuery("SELECT CURRENT_TIMESTAMP() AS foo").wait() + guard let _ = rows[0].column("foo")?.time else { + XCTFail("Could not convert to time: \(rows[0])") + return + } + } + func testNull() throws { let conn = try MySQLConnection.test(on: self.eventLoop).wait() defer { try! conn.close().wait() } From e4847e5a32951589bebf6c5ce9cf9245a936fcd5 Mon Sep 17 00:00:00 2001 From: "Stuart A. Malone" Date: Sun, 20 Feb 2022 07:32:17 -0500 Subject: [PATCH 2/4] Test return value of strptime for success. Signed-off-by: Stuart A. Malone --- Sources/MySQLNIO/MySQLData.swift | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Sources/MySQLNIO/MySQLData.swift b/Sources/MySQLNIO/MySQLData.swift index b05fb5e..3903031 100644 --- a/Sources/MySQLNIO/MySQLData.swift +++ b/Sources/MySQLNIO/MySQLData.swift @@ -445,18 +445,20 @@ public struct MySQLData: CustomStringConvertible, ExpressibleByStringLiteral, Ex if let s = buffer.readString(length: buffer.readableBytes) { var ctime = tm() let format = "%Y-%m-%d %H:%M:%S" - _ = format.withCString { cf in - s.withCString { cs in - strptime(cs, cf, &ctime) + let success = format.withCString { cf in + return s.withCString { cs in + return strptime(cs, cf, &ctime) != nil } } - return MySQLTime(year: UInt16(ctime.tm_year + 1900), - month: UInt16(ctime.tm_mon + 1), - day: UInt16(ctime.tm_mday), - hour: UInt16(ctime.tm_hour), - minute: UInt16(ctime.tm_min), - second: UInt16(ctime.tm_sec), - microsecond: nil) + if success { + return MySQLTime(year: UInt16(ctime.tm_year + 1900), + month: UInt16(ctime.tm_mon + 1), + day: UInt16(ctime.tm_mday), + hour: UInt16(ctime.tm_hour), + minute: UInt16(ctime.tm_min), + second: UInt16(ctime.tm_sec), + microsecond: nil) + } } return nil } From 7b0803b6b15efbe918fdd81671ab8615d1c393f6 Mon Sep 17 00:00:00 2001 From: "Stuart A. Malone" Date: Mon, 21 Feb 2022 12:40:54 -0500 Subject: [PATCH 3/4] Re-implement SQL date parsing in pure Swift. Signed-off-by: Stuart A. Malone --- Sources/MySQLNIO/MySQLData.swift | 21 ++------------------- Sources/MySQLNIO/MySQLTime.swift | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Sources/MySQLNIO/MySQLData.swift b/Sources/MySQLNIO/MySQLData.swift index 3903031..ce33cda 100644 --- a/Sources/MySQLNIO/MySQLData.swift +++ b/Sources/MySQLNIO/MySQLData.swift @@ -442,25 +442,8 @@ public struct MySQLData: CustomStringConvertible, ExpressibleByStringLiteral, Ex default: return nil } case .text: - if let s = buffer.readString(length: buffer.readableBytes) { - var ctime = tm() - let format = "%Y-%m-%d %H:%M:%S" - let success = format.withCString { cf in - return s.withCString { cs in - return strptime(cs, cf, &ctime) != nil - } - } - if success { - return MySQLTime(year: UInt16(ctime.tm_year + 1900), - month: UInt16(ctime.tm_mon + 1), - day: UInt16(ctime.tm_mday), - hour: UInt16(ctime.tm_hour), - minute: UInt16(ctime.tm_min), - second: UInt16(ctime.tm_sec), - microsecond: nil) - } - } - return nil + guard let s = buffer.readString(length: buffer.readableBytes) else { return nil } + return MySQLTime(s) } } diff --git a/Sources/MySQLNIO/MySQLTime.swift b/Sources/MySQLNIO/MySQLTime.swift index 8ba2a54..e3f20d6 100644 --- a/Sources/MySQLNIO/MySQLTime.swift +++ b/Sources/MySQLNIO/MySQLTime.swift @@ -75,6 +75,24 @@ public struct MySQLTime: Equatable, MySQLDataConvertible { ) } + /// Parse a new `MySQLTime` from a String in "yyyy-MM-dd hh:mm:ss" format. + public init?(_ string: String) { + let parts = string.split { c in + ":- ".contains(c) + } + guard parts.count >= 6, + let year = UInt16(parts[0]), + let month = UInt16(parts[1]), + let day = UInt16(parts[2]), + let hour = UInt16(parts[3]), + let minute = UInt16(parts[4]), + let second = UInt16(parts[5]) + else { + return nil + } + self.init(year: year, month: month, day: day, hour: hour, minute: minute, second: second) + } + /// `MySQLDataConvertible` conformance. public init?(mysqlData: MySQLData) { guard let time = mysqlData.time else { From cdefb314e1bb57916e5b6c54152e5ab3ee80b420 Mon Sep 17 00:00:00 2001 From: Gwynne Raskind Date: Mon, 8 May 2023 21:26:25 -0500 Subject: [PATCH 4/4] Clean up test --- Tests/MySQLNIOTests/MySQLNIOTests.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Tests/MySQLNIOTests/MySQLNIOTests.swift b/Tests/MySQLNIOTests/MySQLNIOTests.swift index fef56b1..b804d2a 100644 --- a/Tests/MySQLNIOTests/MySQLNIOTests.swift +++ b/Tests/MySQLNIOTests/MySQLNIOTests.swift @@ -640,13 +640,10 @@ final class MySQLNIOTests: XCTestCase { let conn = try MySQLConnection.test(on: self.eventLoop).wait() defer { try! conn.close().wait() } - // MariaDB 10.5 returns timestamp columns in text format. + // The text protocol returns timestamp columns in text format. // Ensure these can be converted to MySQLTime without error. - let rows = try! conn.simpleQuery("SELECT CURRENT_TIMESTAMP() AS foo").wait() - guard let _ = rows[0].column("foo")?.time else { - XCTFail("Could not convert to time: \(rows[0])") - return - } + let rows = try conn.simpleQuery("SELECT CURRENT_TIMESTAMP() AS foo").wait() + XCTAssertNotNil(rows[0].column("foo")?.time) } func testNull() throws {