Skip to content
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

null column support #133

Merged
merged 2 commits into from
Feb 28, 2018
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
4 changes: 3 additions & 1 deletion Sources/MySQL/Connection/PacketParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ extension Packet {
try parser.skipLenEnc() // let schema = try parser.parseLenEncString()
try parser.skipLenEnc() // let tableAlias = try parser.parseLenEncString()
try parser.skipLenEnc() // let table = try parser.parseLenEncString()
let name = try parser.parseLenEncString()
guard let name = try parser.parseLenEncString() else {
throw MySQLError(identifier: "fieldName", reason: "'null' field name.", sourceLocation: .capture())
}
try parser.skipLenEnc() // let columnAlias = try parser.parseLenEncString()
_ = try parser.parseLenEnc() // let originalName = try parser.parseLenEncString()

Expand Down
23 changes: 16 additions & 7 deletions Sources/MySQL/Parsing/ParsingHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,23 +100,30 @@ struct Parser {
}

/// Parses length encoded Data
mutating func parseLenEncData() throws -> Data {
let length = try skipLenEnc()
mutating func parseLenEncData() throws -> Data? {
guard let length = try skipLenEnc() else {
return nil
}

return Data(self.payload[position &- length..<position])
}

/// Parses length encoded Data
mutating func parseLenEncBytes() throws -> ByteBuffer {
let length = try skipLenEnc()
mutating func parseLenEncBytes() throws -> ByteBuffer? {
guard let length = try skipLenEnc() else {
return nil
}

return ByteBuffer(start: self.payload.baseAddress?.advanced(by: position &- length), count: length)
}

/// Skips length encoded data/strings
@discardableResult
mutating func skipLenEnc() throws -> Int {
mutating func skipLenEnc() throws -> Int? {
let length = Int(try parseLenEnc())
if length == 251 {
return nil
}

guard position &+ length <= self.payload.count else {
throw MySQLError(.invalidResponse, source: .capture())
Expand All @@ -128,8 +135,10 @@ struct Parser {
}

/// Parses a length encoded string
mutating func parseLenEncString() throws -> String {
let data = try parseLenEncData()
mutating func parseLenEncString() throws -> String? {
guard let data = try parseLenEncData() else {
return nil
}
return String(data: data, encoding: .utf8) ?? ""
}
}
54 changes: 45 additions & 9 deletions Sources/MySQL/Parsing/Row+Parsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ extension Parser {
case .year: throw MySQLError(.unsupported, source: .capture())
case .newdate: throw MySQLError(.unsupported, source: .capture())
case .varchar:
return .varChar(try self.parseLenEncString())
if let string = try self.parseLenEncString() {
return .varChar(string)
} else {
return .null
}
case .bit:
let length = try byte()
if length > 8 {
Expand All @@ -172,17 +176,41 @@ extension Parser {
case .enum: throw MySQLError(.unsupported, source: .capture())
case .set: throw MySQLError(.unsupported, source: .capture())
case .tinyBlob:
return .tinyBlob(try self.parseLenEncData())
if let data = try parseLenEncData() {
return .tinyBlob(data)
} else {
return .null
}
case .mediumBlob:
return .mediumBlob(try self.parseLenEncData())
if let data = try parseLenEncData() {
return .mediumBlob(data)
} else {
return .null
}
case .longBlob:
return .longBlob(try self.parseLenEncData())
if let data = try parseLenEncData() {
return .longBlob(data)
} else {
return .null
}
case .blob:
return .blob(try self.parseLenEncData())
if let data = try parseLenEncData() {
return .blob(data)
} else {
return .null
}
case .varString:
return .varString(try self.parseLenEncString())
if let string = try self.parseLenEncString() {
return .varString(string)
} else {
return .null
}
case .string:
return .string(try self.parseLenEncString())
if let string = try self.parseLenEncString() {
return .string(string)
} else {
return .null
}
case .geometry: throw MySQLError(.unsupported, source: .capture())
}
}
Expand All @@ -199,7 +227,11 @@ extension Row {
}

/// Decodes the value's Data as a binary type from the provided field
mutating func append(_ value: Data, forField field: Field) throws {
mutating func append(_ value: Data?, forField field: Field) throws {
guard let value = value else {
return append(.null, forField: field)
}

switch field.fieldType {
case .null:
append(.null, forField: field)
Expand All @@ -211,7 +243,11 @@ extension Row {
}

/// Decodes the value's Data as a text expressed type from the provided field
mutating func append(_ value: ByteBuffer, forField field: Field) throws {
mutating func append(_ value: ByteBuffer?, forField field: Field) throws {
guard let value = value else {
return append(.null, forField: field)
}

func makeString() -> String {
return String(data: Data(value), encoding: .utf8) ?? ""
}
Expand Down
14 changes: 9 additions & 5 deletions Sources/MySQL/Query/Binding+Encoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,13 @@ fileprivate struct SingleContainer: SingleValueEncodingContainer {
fileprivate struct RowEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol
{
typealias Key = K

var count: Int

var encoder: MySQLBindingEncoder
var codingPath: [CodingKey] {
get { return encoder.codingPath }
}

public init(encoder: MySQLBindingEncoder) {
self.encoder = encoder
self.count = 0
}

func encode(_ value: Bool, forKey key: K) throws {
Expand All @@ -103,14 +99,22 @@ fileprivate struct RowEncodingContainer<K: CodingKey>: KeyedEncodingContainerPro
try value.encode(to: encoder)
}
}

func encodeIfPresent<T>(_ value: T?, forKey key: K) throws where T : Encodable {
if let value = value {
try encode(value, forKey: key)
} else {
try encoder.context.bindNull()
}
}

func encodeNil(forKey key: K) throws {
try encoder.context.bindNull()
}

func nestedContainer<NestedKey: CodingKey>(
keyedBy keyType: NestedKey.Type, forKey key: K
) -> KeyedEncodingContainer<NestedKey> {
) -> KeyedEncodingContainer<NestedKey> {
return KeyedEncodingContainer(UnsupportedEncodingContainer(encoder: encoder))
}

Expand Down
12 changes: 12 additions & 0 deletions Tests/MySQLTests/MySQLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class MySQLTests: XCTestCase {
("testFailures", testFailures),
("testSingleValueDecoding", testSingleValueDecoding),
("testBool", testBool),
("testNullFieldDecode", testNullFieldDecode),
]

override func setUp() {
Expand Down Expand Up @@ -275,6 +276,17 @@ class MySQLTests: XCTestCase {
XCTAssertEqual(models.first?.bool, true)
XCTAssertEqual(models.last?.bool, false)
}

func testNullFieldDecode() throws {
struct Todo: Codable {
var text: String?
}
try! connection.administrativeQuery("DROP TABLE IF EXISTS nulltest").await(on: poolQueue)
try! connection.administrativeQuery("CREATE TABLE nulltest (text VARCHAR(255))").await(on: poolQueue)
try! connection.administrativeQuery("INSERT INTO nulltest (text) VALUES (NULL)").await(on: poolQueue)
let models = try! connection.all(Todo.self, in: "SELECT * FROM nulltest").await(on: poolQueue)
XCTAssertEqual(models.first?.text, nil)
}
}

struct User: Decodable {
Expand Down