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