Skip to content

add decoding for UUID #1137

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 8 commits into from
Jun 29, 2022
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
42 changes: 24 additions & 18 deletions Sources/SQLite/Typed/Coding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,14 @@ private class SQLiteEncoder: Encoder {
}

func encode<T>(_ value: T, forKey key: Key) throws where T: Swift.Encodable {
if let data = value as? Data {
switch value {
case let data as Data:
encoder.setters.append(Expression(key.stringValue) <- data)
} else if let date = value as? Date {
case let date as Date:
encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue)
} else if let uuid = value as? UUID {
case let uuid as UUID:
encoder.setters.append(Expression(key.stringValue) <- uuid.datatypeValue)
} else {
default:
let encoded = try JSONEncoder().encode(value)
let string = String(data: encoded, encoding: .utf8)
encoder.setters.append(Expression(key.stringValue) <- string)
Expand Down Expand Up @@ -261,7 +262,7 @@ private class SQLiteEncoder: Encoder {
}

func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key)
-> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey {
-> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey {
fatalError("encoding a nested container is not supported")
}

Expand Down Expand Up @@ -381,27 +382,32 @@ private class SQLiteDecoder: Decoder {

func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable {
// swiftlint:disable force_cast
if type == Data.self {
switch type {
case is Data.Type:
let data = try row.get(Expression<Data>(key.stringValue))
return data as! T
} else if type == Date.self {
case is Date.Type:
let date = try row.get(Expression<Date>(key.stringValue))
return date as! T
case is UUID.Type:
let uuid = try row.get(Expression<UUID>(key.stringValue))
return uuid as! T
default:
// swiftlint:enable force_cast
guard let JSONString = try row.get(Expression<String?>(key.stringValue)) else {
Copy link
Collaborator

@jberkel jberkel Jun 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variables should be lowercase actually, not even your changes, ignore

throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath,
debugDescription: "an unsupported type was found"))
}
guard let data = JSONString.data(using: .utf8) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath,
debugDescription: "invalid utf8 data found"))
}
return try JSONDecoder().decode(type, from: data)
}
// swiftlint:enable force_cast
guard let JSONString = try row.get(Expression<String?>(key.stringValue)) else {
throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath,
debugDescription: "an unsupported type was found"))
}
guard let data = JSONString.data(using: .utf8) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath,
debugDescription: "invalid utf8 data found"))
}
return try JSONDecoder().decode(type, from: data)
}

func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws
-> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey {
-> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath,
debugDescription: "decoding nested containers is not supported"))
}
Expand Down
7 changes: 4 additions & 3 deletions Tests/SQLiteTests/QueryIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ class QueryIntegrationTests: SQLiteTestCase {
builder.column(Expression<Double>("float"))
builder.column(Expression<Double>("double"))
builder.column(Expression<Date>("date"))
builder.column(Expression<UUID>("uuid"))
builder.column(Expression<String?>("optional"))
builder.column(Expression<Data>("sub"))
})

let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil)
let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8,
date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1)

date: Date(timeIntervalSince1970: 5000), uuid: testUUIDValue, optional: "optional", sub: value1)
try db.run(table.insert(value))

let rows = try db.prepare(table)
Expand All @@ -95,6 +95,7 @@ class QueryIntegrationTests: SQLiteTestCase {
XCTAssertEqual(values[0].float, 7)
XCTAssertEqual(values[0].double, 8)
XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000))
XCTAssertEqual(values[0].uuid, testUUIDValue)
XCTAssertEqual(values[0].optional, "optional")
XCTAssertEqual(values[0].sub?.int, 1)
XCTAssertEqual(values[0].sub?.string, "2")
Expand Down
47 changes: 25 additions & 22 deletions Tests/SQLiteTests/QueryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,12 @@ class QueryTests: XCTestCase {
func test_insert_encodable() throws {
let emails = Table("emails")
let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil)
let insert = try emails.insert(value)
assertSQL(
"""
INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\")
VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000')
INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\")
VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F')
""".replacingOccurrences(of: "\n", with: ""),
insert
)
Expand All @@ -294,16 +294,17 @@ class QueryTests: XCTestCase {
func test_insert_encodable_with_nested_encodable() throws {
let emails = Table("emails")
let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil)
let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
date: Date(timeIntervalSince1970: 0), optional: "optional", sub: value1)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: "optional", sub: value1)
let insert = try emails.insert(value)
let encodedJSON = try JSONEncoder().encode(value1)
let encodedJSONString = String(data: encodedJSON, encoding: .utf8)!
assertSQL(
"""
INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"optional\",
\"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'optional', '\(encodedJSONString)')
INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\",
\"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F',
'optional', '\(encodedJSONString)')
""".replacingOccurrences(of: "\n", with: ""),
insert
)
Expand Down Expand Up @@ -350,14 +351,15 @@ class QueryTests: XCTestCase {
let emails = Table("emails")
let string = Expression<String>("string")
let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil)
let insert = try emails.upsert(value, onConflictOf: string)
assertSQL(
"""
INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\")
VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') ON CONFLICT (\"string\")
INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\")
VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') ON CONFLICT (\"string\")
DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\",
\"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\"
\"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\",
\"uuid\" = \"excluded\".\"uuid\"
""".replacingOccurrences(of: "\n", with: ""),
insert
)
Expand All @@ -366,17 +368,18 @@ class QueryTests: XCTestCase {
func test_insert_many_encodable() throws {
let emails = Table("emails")
let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil)
let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil)
let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil)
let insert = try emails.insertMany([value1, value2, value3])
assertSQL(
"""
INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\")
VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000'),
(3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000')
INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\")
VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'),
(2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'),
(3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F')
""".replacingOccurrences(of: "\n", with: ""),
insert
)
Expand All @@ -399,12 +402,12 @@ class QueryTests: XCTestCase {
func test_update_encodable() throws {
let emails = Table("emails")
let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil)
let update = try emails.update(value)
assertSQL(
"""
UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0,
\"date\" = '1970-01-01T00:00:00.000'
\"date\" = '1970-01-01T00:00:00.000', \"uuid\" = 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'
""".replacingOccurrences(of: "\n", with: ""),
update
)
Expand All @@ -413,9 +416,9 @@ class QueryTests: XCTestCase {
func test_update_encodable_with_nested_encodable() throws {
let emails = Table("emails")
let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil)
let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
date: Date(timeIntervalSince1970: 0), optional: nil, sub: value1)
date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: value1)
let update = try emails.update(value)

// NOTE: As Linux JSON decoding doesn't order keys the same way, we need to check prefix, suffix,
Expand All @@ -424,7 +427,7 @@ class QueryTests: XCTestCase {
let expectedPrefix =
"""
UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0,
\"date\" = '1970-01-01T00:00:00.000', \"sub\" = '
\"date\" = '1970-01-01T00:00:00.000', \"uuid\" = 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', \"sub\" = '
""".replacingOccurrences(of: "\n", with: "")
let expectedSuffix = "'"

Expand Down
7 changes: 6 additions & 1 deletion Tests/SQLiteTests/TestHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ let stringOptional = Expression<String?>("stringOptional")
let uuid = Expression<UUID>("uuid")
let uuidOptional = Expression<UUID?>("uuidOptional")

let testUUIDValue = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!

func assertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible,
file: StaticString = #file, line: UInt = #line) {
XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line)
Expand All @@ -115,16 +117,18 @@ class TestCodable: Codable, Equatable {
let float: Float
let double: Double
let date: Date
let uuid: UUID
let optional: String?
let sub: TestCodable?

init(int: Int, string: String, bool: Bool, float: Float, double: Double, date: Date, optional: String?, sub: TestCodable?) {
init(int: Int, string: String, bool: Bool, float: Float, double: Double, date: Date, uuid: UUID, optional: String?, sub: TestCodable?) {
self.int = int
self.string = string
self.bool = bool
self.float = float
self.double = double
self.date = date
self.uuid = uuid
self.optional = optional
self.sub = sub
}
Expand All @@ -136,6 +140,7 @@ class TestCodable: Codable, Equatable {
lhs.float == rhs.float &&
lhs.double == rhs.double &&
lhs.date == rhs.date &&
lhs.uuid == lhs.uuid &&
lhs.optional == rhs.optional &&
lhs.sub == rhs.sub
}
Expand Down