Skip to content

Commit 8f9e1ca

Browse files
committed
data refactor passing tests
1 parent 3329e73 commit 8f9e1ca

File tree

6 files changed

+188
-113
lines changed

6 files changed

+188
-113
lines changed

Sources/FluentPostgreSQL/PostgreSQLDataDecoder.swift

Lines changed: 0 additions & 36 deletions
This file was deleted.

Sources/FluentPostgreSQL/PostgreSQLDataEncoder.swift

Lines changed: 0 additions & 34 deletions
This file was deleted.

Sources/FluentPostgreSQL/PostgreSQLDatabase+QuerySupporting.swift

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,12 @@ extension PostgreSQLDatabase: QuerySupporting {
1818
// Dictionary values should be added to the parameterized array.
1919
let modelData: [PostgreSQLData]
2020
if let model = query.data {
21-
let encoded = try CodableDataEncoder().encode(model)
22-
switch encoded {
23-
case .dictionary(let dict):
24-
sqlQuery.columns += dict.keys.map { key in
25-
return DataColumn(table: query.entity, name: key)
26-
}
27-
modelData = try dict.values.map { codableData -> PostgreSQLData in
28-
switch codableData {
29-
case .single(let closure):
30-
var encoder: SingleValueEncodingContainer = PostgreSQLDataEncoder()
31-
try closure(&encoder)
32-
return (encoder as! PostgreSQLDataEncoder).data!
33-
case .array, .dictionary, .null:
34-
throw PostgreSQLError(
35-
identifier: "nestedData",
36-
reason: "Unsupported nested data in query.",
37-
suggestedFixes: [
38-
"Use a nested struct instead"
39-
]
40-
)
41-
}
42-
}
43-
default:
44-
throw PostgreSQLError(
45-
identifier: "queryData",
46-
reason: "Unsupported PostgreSQLData (dictionary required) created by query data: \(model)"
47-
)
21+
let encoder = PostgreSQLRowEncoder()
22+
try model.encode(to: encoder)
23+
sqlQuery.columns += encoder.data.keys.map { key in
24+
return DataColumn(table: query.entity, name: key)
4825
}
26+
modelData = .init(encoder.data.values)
4927
} else {
5028
modelData = []
5129
}
@@ -78,15 +56,8 @@ extension PostgreSQLDatabase: QuerySupporting {
7856

7957
// Run the query
8058
return try connection.query(sqlString, parameters) { row in
81-
let codableDict = row.mapValues { psqlData -> DecodableData in
82-
if psqlData.data == nil {
83-
return .null
84-
} else {
85-
return .single({ PostgreSQLDataDecoder(data: psqlData) })
86-
}
87-
}
8859
do {
89-
let decoded = try CodableDataDecoder().decode(D.self, from: .dictionary(codableDict))
60+
let decoded = try D.init(from: PostgreSQLRowDecoder(row: row))
9061
pushStream.push(decoded)
9162
} catch {
9263
pushStream.error(error)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
internal final class PostgreSQLRowDecoder: Decoder {
2+
var codingPath: [CodingKey]
3+
var userInfo: [CodingUserInfoKey: Any]
4+
let data: [String: PostgreSQLData]
5+
init(row: [String: PostgreSQLData]) {
6+
self.data = row
7+
self.codingPath = []
8+
self.userInfo = [:]
9+
}
10+
11+
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
12+
let container = PostgreSQLRowKeyedDecodingContainer<Key>(decoder: self)
13+
return KeyedDecodingContainer(container)
14+
}
15+
16+
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
17+
throw unsupported
18+
}
19+
20+
func singleValueContainer() throws -> SingleValueDecodingContainer {
21+
throw unsupported
22+
}
23+
24+
func require(key: CodingKey) throws -> PostgreSQLData {
25+
guard let data = self.data[key.stringValue] else {
26+
throw PostgreSQLError(identifier: "decode", reason: "No value found at key: \(key)")
27+
}
28+
return data
29+
}
30+
31+
}
32+
33+
private let unsupported = PostgreSQLError(
34+
identifier: "rowDecode",
35+
reason: "PostgreSQL rows only support a flat, keyed structure `[String: T]`",
36+
suggestedFixes: [
37+
"You can conform nested types to `PostgreSQLJSONType` or `PostgreSQLArrayType`. (Nested types must be `PostgreSQLDataCustomConvertible`.)"
38+
]
39+
)
40+
41+
42+
fileprivate struct PostgreSQLRowKeyedDecodingContainer<K>: KeyedDecodingContainerProtocol
43+
where K: CodingKey
44+
{
45+
var allKeys: [K]
46+
typealias Key = K
47+
var codingPath: [CodingKey]
48+
let decoder: PostgreSQLRowDecoder
49+
init(decoder: PostgreSQLRowDecoder) {
50+
self.decoder = decoder
51+
codingPath = []
52+
allKeys = self.decoder.data.keys.flatMap { K(stringValue: $0) }
53+
}
54+
func contains(_ key: K) -> Bool { return decoder.data.keys.contains(key.stringValue) }
55+
func decodeNil(forKey key: K) -> Bool { return decoder.data[key.stringValue]?.data == nil }
56+
func decode(_ type: Int.Type, forKey key: K) throws -> Int { return try decoder.require(key: key).decode(Int.self) }
57+
func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 { return try decoder.require(key: key).decode(Int8.self) }
58+
func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 { return try decoder.require(key: key).decode(Int16.self) }
59+
func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 { return try decoder.require(key: key).decode(Int32.self) }
60+
func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 { return try decoder.require(key: key).decode(Int64.self) }
61+
func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { return try decoder.require(key: key).decode(UInt.self) }
62+
func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { return try decoder.require(key: key).decode(UInt8.self) }
63+
func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { return try decoder.require(key: key).decode(UInt16.self) }
64+
func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { return try decoder.require(key: key).decode(UInt32.self) }
65+
func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { return try decoder.require(key: key).decode(UInt64.self) }
66+
func decode(_ type: Double.Type, forKey key: K) throws -> Double { return try decoder.require(key: key).decode(Double.self) }
67+
func decode(_ type: Float.Type, forKey key: K) throws -> Float { return try decoder.require(key: key).decode(Float.self) }
68+
func decode(_ type: Bool.Type, forKey key: K) throws -> Bool { return try decoder.require(key: key).decode(Bool.self) }
69+
func decode(_ type: String.Type, forKey key: K) throws -> String { return try decoder.require(key: key).decode(String.self) }
70+
func decode<T>(_ type: T.Type, forKey key: K) throws -> T where T: Decodable {
71+
guard let convertible = type as? PostgreSQLDataCustomConvertible.Type else {
72+
throw PostgreSQLError(
73+
identifier: "convertible",
74+
reason: "Unsupported decodable type: \(type)",
75+
suggestedFixes: [
76+
"Conform \(type) to PostgreSQLDataCustomConvertible"
77+
]
78+
)
79+
}
80+
return try convertible.convertFromPostgreSQLData(decoder.require(key: key)) as! T
81+
}
82+
83+
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
84+
return try decoder.container(keyedBy: NestedKey.self)
85+
}
86+
87+
func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer {
88+
return try decoder.unkeyedContainer()
89+
}
90+
91+
func superDecoder() throws -> Decoder { return decoder }
92+
func superDecoder(forKey key: K) throws -> Decoder { return decoder }
93+
94+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
internal final class PostgreSQLRowEncoder: Encoder {
2+
var codingPath: [CodingKey]
3+
var userInfo: [CodingUserInfoKey : Any]
4+
var data: [String: PostgreSQLData]
5+
init() {
6+
self.codingPath = []
7+
self.userInfo = [:]
8+
self.data = [:]
9+
}
10+
11+
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
12+
let container = PostgreSQLRowKeyedEncodingContainer<Key>(encoder: self)
13+
return KeyedEncodingContainer(container)
14+
}
15+
16+
func unkeyedContainer() -> UnkeyedEncodingContainer {
17+
unsupported()
18+
}
19+
20+
func singleValueContainer() -> SingleValueEncodingContainer {
21+
unsupported()
22+
}
23+
24+
}
25+
26+
private func unsupported() -> Never {
27+
fatalError("""
28+
PostgreSQL rows only support a flat, keyed structure `[String: T]`.
29+
30+
Query data must be an encodable dictionary, struct, or class.
31+
32+
You can also conform nested types to `PostgreSQLJSONType` or `PostgreSQLArrayType`. (Nested types must be `PostgreSQLDataCustomConvertible`.)
33+
""")
34+
}
35+
36+
fileprivate struct PostgreSQLRowKeyedEncodingContainer<K>: KeyedEncodingContainerProtocol
37+
where K: CodingKey
38+
{
39+
var codingPath: [CodingKey]
40+
let encoder: PostgreSQLRowEncoder
41+
init(encoder: PostgreSQLRowEncoder) {
42+
self.encoder = encoder
43+
self.codingPath = []
44+
}
45+
46+
mutating func encodeNil(forKey key: K) throws { encoder.data[key.stringValue] = PostgreSQLData(type: .void) }
47+
mutating func encode(_ value: Bool, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
48+
mutating func encode(_ value: Int, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
49+
mutating func encode(_ value: Int16, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
50+
mutating func encode(_ value: Int32, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
51+
mutating func encode(_ value: Int64, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
52+
mutating func encode(_ value: UInt, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
53+
mutating func encode(_ value: UInt8, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
54+
mutating func encode(_ value: UInt16, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
55+
mutating func encode(_ value: UInt32, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
56+
mutating func encode(_ value: UInt64, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
57+
mutating func encode(_ value: Double, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
58+
mutating func encode(_ value: Float, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
59+
mutating func encode(_ value: String, forKey key: K) throws { encoder.data[key.stringValue] = try value.convertToPostgreSQLData() }
60+
mutating func superEncoder() -> Encoder { return encoder }
61+
mutating func superEncoder(forKey key: K) -> Encoder { return encoder }
62+
mutating func encode<T>(_ value: T, forKey key: K) throws where T : Encodable {
63+
guard let convertible = value as? PostgreSQLDataCustomConvertible else {
64+
let type = Swift.type(of: value)
65+
throw PostgreSQLError(
66+
identifier: "convertible",
67+
reason: "Unsupported encodable type: \(type)",
68+
suggestedFixes: [
69+
"Conform \(type) to PostgreSQLDataCustomConvertible"
70+
]
71+
)
72+
}
73+
encoder.data[key.stringValue] = try convertible.convertToPostgreSQLData()
74+
}
75+
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: K) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
76+
return encoder.container(keyedBy: NestedKey.self)
77+
}
78+
79+
mutating func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer {
80+
return encoder.unkeyedContainer()
81+
}
82+
}

Tests/FluentPostgreSQLTests/FluentPostgreSQLTests.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,11 @@ class FluentPostgreSQLTests: XCTestCase {
4444

4545
func testNestedStruct() throws {
4646
let conn = try! database.makeConnection(using: .init(), on: eventLoop).await(on: eventLoop)
47-
print(User.codingPath(forKey: \.favoriteColors))
48-
print(User.properties())
47+
// print(User.codingPath(forKey: \.favoriteColors))
48+
// print(User.properties())
4949
try! User.prepare(on: conn).await(on: eventLoop)
50-
51-
52-
let user = try! User(id: nil, name: "Tanner", pet: Pet(name: "Zizek"))
53-
user.favoriteColors = ["pink", "blue"]
50+
let user = User(id: nil, name: "Tanner", pet: Pet(name: "Zizek"))
51+
// user.favoriteColors = ["pink", "blue"]
5452
_ = try! user.save(on: conn).await(on: eventLoop)
5553

5654
let fetched = try! User.query(on: conn).first().await(on: eventLoop)
@@ -80,11 +78,11 @@ final class User: PostgreSQLModel, Migration {
8078
var id: Int?
8179
var name: String
8280
var age: Int?
83-
var favoriteColors: [String]
81+
// var favoriteColors: [String]
8482
var pet: Pet
8583

8684
init(id: Int? = nil, name: String, pet: Pet) {
87-
self.favoriteColors = []
85+
// self.favoriteColors = []
8886
self.id = id
8987
self.name = name
9088
self.pet = pet

0 commit comments

Comments
 (0)