forked from vapor/postgres-nio
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPostgresRow.swift
324 lines (282 loc) · 10.5 KB
/
PostgresRow.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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
import NIOCore
import class Foundation.JSONDecoder
/// `PostgresRow` represents a single table row that is received from the server for a query or a prepared statement.
/// Its element type is ``PostgresCell``.
///
/// - Warning: Please note that random access to cells in a ``PostgresRow`` have O(n) time complexity. If you require
/// random access to cells in O(1) create a new ``PostgresRandomAccessRow`` with the given row and
/// access it instead.
public struct PostgresRow: Sendable {
@usableFromInline
let lookupTable: [String: Int]
@usableFromInline
let data: DataRow
@usableFromInline
let columns: [RowDescription.Column]
init(data: DataRow, lookupTable: [String: Int], columns: [RowDescription.Column]) {
self.data = data
self.lookupTable = lookupTable
self.columns = columns
}
}
extension PostgresRow: Equatable {
public static func ==(lhs: Self, rhs: Self) -> Bool {
// we don't need to compare the lookup table here, as the looup table is only derived
// from the column description.
lhs.data == rhs.data && lhs.columns == rhs.columns
}
}
extension PostgresRow: Sequence {
public typealias Element = PostgresCell
public struct Iterator: IteratorProtocol {
public typealias Element = PostgresCell
private(set) var columnIndex: Array<RowDescription.Column>.Index
private(set) var columnIterator: Array<RowDescription.Column>.Iterator
private(set) var dataIterator: DataRow.Iterator
init(_ row: PostgresRow) {
self.columnIndex = 0
self.columnIterator = row.columns.makeIterator()
self.dataIterator = row.data.makeIterator()
}
public mutating func next() -> PostgresCell? {
guard let bytes = self.dataIterator.next() else {
return nil
}
let column = self.columnIterator.next()!
defer { self.columnIndex += 1 }
return PostgresCell(
bytes: bytes,
dataType: column.dataType,
format: column.format,
columnName: column.name,
columnIndex: columnIndex
)
}
}
public func makeIterator() -> Iterator {
Iterator(self)
}
}
extension PostgresRow: Collection {
public struct Index: Comparable {
var cellIndex: DataRow.Index
var columnIndex: Array<RowDescription.Column>.Index
// Only needed implementation for comparable. The compiler synthesizes the rest from this.
public static func < (lhs: Self, rhs: Self) -> Bool {
lhs.columnIndex < rhs.columnIndex
}
}
public subscript(position: Index) -> PostgresCell {
let column = self.columns[position.columnIndex]
return PostgresCell(
bytes: self.data[position.cellIndex],
dataType: column.dataType,
format: column.format,
columnName: column.name,
columnIndex: position.columnIndex
)
}
public var startIndex: Index {
Index(
cellIndex: self.data.startIndex,
columnIndex: 0
)
}
public var endIndex: Index {
Index(
cellIndex: self.data.endIndex,
columnIndex: self.columns.count
)
}
public func index(after i: Index) -> Index {
Index(
cellIndex: self.data.index(after: i.cellIndex),
columnIndex: self.columns.index(after: i.columnIndex)
)
}
public var count: Int {
self.data.count
}
}
extension PostgresRow {
public func makeRandomAccess() -> PostgresRandomAccessRow {
PostgresRandomAccessRow(self)
}
}
/// A random access row of ``PostgresCell``s. Its initialization is O(n) where n is the number of columns
/// in the row. All subsequent cell access are O(1).
public struct PostgresRandomAccessRow {
let columns: [RowDescription.Column]
let cells: [ByteBuffer?]
let lookupTable: [String: Int]
public init(_ row: PostgresRow) {
self.cells = [ByteBuffer?](row.data)
self.columns = row.columns
self.lookupTable = row.lookupTable
}
}
extension PostgresRandomAccessRow: Sendable, RandomAccessCollection {
public typealias Element = PostgresCell
public typealias Index = Int
public var startIndex: Int {
0
}
public var endIndex: Int {
self.columns.count
}
public var count: Int {
self.columns.count
}
public subscript(index: Int) -> PostgresCell {
guard index < self.endIndex else {
preconditionFailure("index out of bounds")
}
let column = self.columns[index]
return PostgresCell(
bytes: self.cells[index],
dataType: column.dataType,
format: column.format,
columnName: column.name,
columnIndex: index
)
}
public subscript(name: String) -> PostgresCell {
guard let index = self.lookupTable[name] else {
fatalError(#"A column "\#(name)" does not exist."#)
}
return self[index]
}
/// Checks if the row contains a cell for the given column name.
/// - Parameter column: The column name to check against
/// - Returns: `true` if the row contains this column, `false` if it does not.
public func contains(_ column: String) -> Bool {
self.lookupTable[column] != nil
}
}
extension PostgresRandomAccessRow {
public subscript(data index: Int) -> PostgresData {
guard index < self.endIndex else {
preconditionFailure("index out of bounds")
}
let column = self.columns[index]
return PostgresData(
type: column.dataType,
typeModifier: column.dataTypeModifier,
formatCode: .binary,
value: self.cells[index]
)
}
public subscript(data column: String) -> PostgresData {
guard let index = self.lookupTable[column] else {
fatalError(#"A column "\#(column)" does not exist."#)
}
return self[data: index]
}
}
extension PostgresRandomAccessRow {
/// Access the data in the provided column and decode it into the target type.
///
/// - Parameters:
/// - column: The column name to read the data from
/// - type: The type to decode the data into
/// - Throws: The error of the decoding implementation. See also `PSQLDecodable` protocol for this.
/// - Returns: The decoded value of Type T.
func decode<T: PostgresDecodable, JSONDecoder: PostgresJSONDecoder>(
column: String,
as type: T.Type,
context: PostgresDecodingContext<JSONDecoder>,
file: String = #fileID, line: Int = #line
) throws -> T {
guard let index = self.lookupTable[column] else {
fatalError(#"A column "\#(column)" does not exist."#)
}
return try self.decode(column: index, as: type, context: context, file: file, line: line)
}
/// Access the data in the provided column and decode it into the target type.
///
/// - Parameters:
/// - column: The column index to read the data from
/// - type: The type to decode the data into
/// - Throws: The error of the decoding implementation. See also `PSQLDecodable` protocol for this.
/// - Returns: The decoded value of Type T.
func decode<T: PostgresDecodable, JSONDecoder: PostgresJSONDecoder>(
column index: Int,
as type: T.Type,
context: PostgresDecodingContext<JSONDecoder>,
file: String = #fileID, line: Int = #line
) throws -> T {
precondition(index < self.columns.count)
let column = self.columns[index]
var cellSlice = self.cells[index]
do {
return try T._decodeRaw(from: &cellSlice, type: column.dataType, format: column.format, context: context)
} catch let code as PostgresDecodingError.Code {
throw PostgresDecodingError(
code: code,
columnName: self.columns[index].name,
columnIndex: index,
targetType: T.self,
postgresType: self.columns[index].dataType,
postgresFormat: self.columns[index].format,
postgresData: cellSlice,
file: file,
line: line
)
}
}
}
// MARK: Deprecated API
extension PostgresRow {
@available(*, deprecated, message: "Will be removed from public API.")
public var rowDescription: PostgresMessage.RowDescription {
let fields = self.columns.map { column in
PostgresMessage.RowDescription.Field(
name: column.name,
tableOID: UInt32(column.tableOID),
columnAttributeNumber: column.columnAttributeNumber,
dataType: PostgresDataType(UInt32(column.dataType.rawValue)),
dataTypeSize: column.dataTypeSize,
dataTypeModifier: column.dataTypeModifier,
formatCode: .init(psqlFormatCode: column.format)
)
}
return PostgresMessage.RowDescription(fields: fields)
}
@available(*, deprecated, message: "Iterate the cells on `PostgresRow` instead.")
public var dataRow: PostgresMessage.DataRow {
let columns = self.data.map {
PostgresMessage.DataRow.Column(value: $0)
}
return PostgresMessage.DataRow(columns: columns)
}
@available(*, deprecated, message: """
This call is O(n) where n is the number of cells in the row. For random access to cells
in a row create a PostgresRandomAccessRow from the row first and use its subscript
methods. (see `makeRandomAccess()`)
""")
public func column(_ column: String) -> PostgresData? {
guard let index = self.lookupTable[column] else {
return nil
}
return PostgresData(
type: self.columns[index].dataType,
typeModifier: self.columns[index].dataTypeModifier,
formatCode: .binary,
value: self.data[column: index]
)
}
}
extension PostgresRow: CustomStringConvertible {
public var description: String {
var row: [String: PostgresData] = [:]
for cell in self {
row[cell.columnName] = PostgresData(
type: cell.dataType,
typeModifier: 0,
formatCode: cell.format,
value: cell.bytes
)
}
return row.description
}
}