Skip to content

Commit d82d1f0

Browse files
committed
improve psql coders
1 parent ebfe647 commit d82d1f0

16 files changed

+1400
-1634
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
public struct PostgreSQLDataDecoder {
2+
/// Creates a new `PostgreSQLDataDecoder`.
3+
public init() {}
4+
5+
public func decode<D>(_ type: D.Type, from data: PostgreSQLData) throws -> D where D: Decodable {
6+
return try D(from: _Decoder(data: data))
7+
}
8+
9+
// MARK: Private
10+
11+
private struct _Decoder: Decoder {
12+
let codingPath: [CodingKey] = []
13+
var userInfo: [CodingUserInfoKey: Any] = [:]
14+
let data: PostgreSQLData
15+
16+
init(data: PostgreSQLData) {
17+
self.data = data
18+
}
19+
20+
struct DecoderUnwrapper: Decodable {
21+
let decoder: Decoder
22+
init(from decoder: Decoder) throws {
23+
self.decoder = decoder
24+
}
25+
}
26+
27+
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
28+
let json: Data
29+
switch data.type {
30+
case .jsonb:
31+
switch data.storage {
32+
case .binary(let data):
33+
assert(data[data.startIndex] == 0x01, "invalid JSONB data format")
34+
json = data.advanced(by: 1)
35+
default: throw DecodingError.typeMismatch(Data.self, .init(codingPath: codingPath, debugDescription: "Could not decode keyed data from \(data.type): \(data)."))
36+
}
37+
default: throw DecodingError.typeMismatch(Data.self, .init(codingPath: codingPath, debugDescription: "Could not decode keyed data from \(data.type): \(data)."))
38+
}
39+
let unwrapper = try JSONDecoder().decode(DecoderUnwrapper.self, from: json)
40+
return try unwrapper.decoder.container(keyedBy: Key.self)
41+
}
42+
43+
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
44+
struct ArrayMetadata {
45+
/// Unknown
46+
private let _b: Int32
47+
48+
/// The big-endian array element type
49+
private let _type: Int32
50+
51+
/// The big-endian length of the array
52+
private let _count: Int32
53+
54+
/// The big-endian number of dimensions
55+
private let _dimensions: Int32
56+
57+
/// Converts the raw array elemetn type to DataType
58+
var type: PostgreSQLDataType {
59+
return .init(_type.bigEndian)
60+
}
61+
62+
/// The length of the array
63+
var count: Int32 {
64+
return _count.bigEndian
65+
}
66+
67+
/// The number of dimensions
68+
var dimensions: Int32 {
69+
return _dimensions.bigEndian
70+
}
71+
}
72+
73+
switch data.storage {
74+
case .binary(var value):
75+
/// Extract and convert each element.
76+
var array: [PostgreSQLData] = []
77+
78+
let hasData = value.extract(Int32.self).bigEndian
79+
if hasData == 1 {
80+
/// grab the array metadata from the beginning of the data
81+
let metadata = value.extract(ArrayMetadata.self)
82+
for _ in 0..<metadata.count {
83+
let count = Int(value.extract(Int32.self).bigEndian)
84+
let subValue = value.extract(count: count)
85+
let psqlData = PostgreSQLData(metadata.type, binary: subValue)
86+
array.append(psqlData)
87+
}
88+
} else {
89+
array = []
90+
}
91+
92+
print(array)
93+
fatalError("Unimplemented.")
94+
default: fatalError()
95+
}
96+
}
97+
98+
func singleValueContainer() throws -> SingleValueDecodingContainer {
99+
return _SingleValueDecodingContainer(data: data)
100+
}
101+
}
102+
103+
private struct _SingleValueDecodingContainer: SingleValueDecodingContainer {
104+
let codingPath: [CodingKey] = []
105+
let data: PostgreSQLData
106+
107+
init(data: PostgreSQLData) {
108+
self.data = data
109+
}
110+
111+
func typeMismatch<T>(_ type: T.Type) -> Error {
112+
return DecodingError.typeMismatch(type, .init(codingPath: codingPath, debugDescription: "Could not decode \(type) from \(data.type): \(data)"))
113+
}
114+
115+
func valueNotFound<T>(_ type: T.Type) -> Error {
116+
return DecodingError.valueNotFound(type, .init(codingPath: codingPath, debugDescription: "Could not decode \(type) from null."))
117+
}
118+
119+
public func decodeNil() -> Bool {
120+
switch data.storage {
121+
case .null: return true
122+
default: return false
123+
}
124+
}
125+
126+
public func decode(_ type: Bool.Type) throws -> Bool {
127+
switch data.storage {
128+
case .text(let value):
129+
guard value.count == 1 else {
130+
throw typeMismatch(type)
131+
}
132+
switch value[value.startIndex] {
133+
case "t": return true
134+
case "f": return false
135+
default: throw typeMismatch(type)
136+
}
137+
case .binary(let value):
138+
guard value.count == 1 else {
139+
throw typeMismatch(type)
140+
}
141+
switch value[0] {
142+
case 1: return true
143+
case 0: return false
144+
default: throw typeMismatch(type)
145+
}
146+
case .null: throw valueNotFound(type)
147+
}
148+
}
149+
150+
public func decode(_ type: String.Type) throws -> String {
151+
switch data.storage {
152+
case .text(let string): return string
153+
case .binary(let value):
154+
switch data.type {
155+
case .text, .name, .varchar, .bpchar:
156+
guard let string = String(data: value, encoding: .utf8) else {
157+
throw DecodingError.dataCorrupted(.init(codingPath: codingPath, debugDescription: "Non-UTF8 String."))
158+
}
159+
return string
160+
case .point: return try decode(PostgreSQLPoint.self).description
161+
case .uuid: return try decode(UUID.self).uuidString
162+
case .numeric:
163+
/// Represents the meta information preceeding a numeric value.
164+
/// - note: all values must be accessed adding `.bigEndian`
165+
struct PostgreSQLNumericMetadata {
166+
/// The number of digits after this metadata
167+
var ndigits: Int16
168+
/// How many of the digits are before the decimal point (always add 1)
169+
var weight: Int16
170+
/// If 1, this number is negative. Otherwise, positive.
171+
var sign: Int16
172+
/// The number of sig digits after the decimal place (get rid of trailing 0s)
173+
var dscale: Int16
174+
}
175+
176+
/// create mutable value since we will be using `.extract` which advances the buffer's view
177+
var value = value
178+
179+
/// grab the numeric metadata from the beginning of the array
180+
let metadata = value.extract(PostgreSQLNumericMetadata.self)
181+
182+
var integer = ""
183+
var fractional = ""
184+
for offset in 0..<metadata.ndigits.bigEndian {
185+
/// extract current char and advance memory
186+
let char = value.extract(Int16.self).bigEndian
187+
188+
/// conver the current char to its string form
189+
let string: String
190+
if char == 0 {
191+
/// 0 means 4 zeros
192+
string = "0000"
193+
} else {
194+
string = char.description
195+
}
196+
197+
/// depending on our offset, append the string to before or after the decimal point
198+
if offset < metadata.weight.bigEndian + 1 {
199+
integer += string
200+
} else {
201+
// Leading zeros matter with fractional
202+
fractional += fractional.count == 0 ? String(repeating: "0", count: 4 - string.count) + string : string
203+
}
204+
}
205+
206+
/// use the dscale to remove extraneous zeroes at the end of the fractional part
207+
let lastSignificantIndex = fractional.index(fractional.startIndex, offsetBy: Int(metadata.dscale.bigEndian))
208+
fractional = String(fractional[..<lastSignificantIndex])
209+
210+
/// determine whether fraction is empty and dynamically add `.`
211+
let numeric: String
212+
if fractional != "" {
213+
numeric = integer + "." + fractional
214+
} else {
215+
numeric = integer
216+
}
217+
218+
/// use sign to determine adding a leading `-`
219+
if metadata.sign.bigEndian == 1 {
220+
return "-" + numeric
221+
} else {
222+
return numeric
223+
}
224+
default: throw typeMismatch(type)
225+
}
226+
case .null: throw valueNotFound(type)
227+
}
228+
}
229+
230+
public func decode(_ type: Double.Type) throws -> Double {
231+
fatalError()
232+
}
233+
234+
public func decode(_ type: Float.Type) throws -> Float {
235+
fatalError()
236+
}
237+
238+
public func decode(_ type: Int.Type) throws -> Int {
239+
fatalError()
240+
}
241+
242+
public func decode(_ type: Int8.Type) throws -> Int8 {
243+
fatalError()
244+
}
245+
246+
public func decode(_ type: Int16.Type) throws -> Int16 {
247+
fatalError()
248+
}
249+
250+
public func decode(_ type: Int32.Type) throws -> Int32 {
251+
fatalError()
252+
}
253+
254+
public func decode(_ type: Int64.Type) throws -> Int64 {
255+
fatalError()
256+
}
257+
258+
public func decode(_ type: UInt.Type) throws -> UInt {
259+
fatalError()
260+
}
261+
262+
public func decode(_ type: UInt8.Type) throws -> UInt8 {
263+
fatalError()
264+
}
265+
266+
public func decode(_ type: UInt16.Type) throws -> UInt16 {
267+
fatalError()
268+
}
269+
270+
public func decode(_ type: UInt32.Type) throws -> UInt32 {
271+
fatalError()
272+
}
273+
274+
public func decode(_ type: UInt64.Type) throws -> UInt64 {
275+
fatalError()
276+
}
277+
278+
func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
279+
guard let convertible = type as? PostgreSQLDataConvertible.Type else {
280+
return try T(from: _Decoder(data: data))
281+
}
282+
return try convertible.convertFromPostgreSQLData(data) as! T
283+
}
284+
}
285+
}

0 commit comments

Comments
 (0)