forked from vapor/postgres-kit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPostgreSQLArrayCustomConvertible.swift
151 lines (123 loc) · 5.57 KB
/
PostgreSQLArrayCustomConvertible.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
import Foundation
/// Representable by a `T[]` column on the PostgreSQL database.
public protocol PostgreSQLArrayCustomConvertible: PostgreSQLDataCustomConvertible, Codable {
/// The associated array element type
associatedtype PostgreSQLArrayElement // :PostgreSQLDataCustomConvertible
/// Convert an array of elements to self.
static func convertFromPostgreSQLArray(_ data: [PostgreSQLArrayElement]) -> Self
/// Convert self to an array of elements.
func convertToPostgreSQLArray() -> [PostgreSQLArrayElement]
}
extension PostgreSQLArrayCustomConvertible {
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Self {
guard var value = data.data else {
throw PostgreSQLError(identifier: "nullArray", reason: "Unable to decode PostgreSQL array from `null` data.")
}
/// Extract and convert each element.
var array: [PostgreSQLArrayElement] = []
let hasData = value.extract(Int32.self).bigEndian
if hasData == 1 {
/// grab the array metadata from the beginning of the data
let metadata = value.extract(PostgreSQLArrayMetadata.self)
guard let type = PostgreSQLArrayElement.self as? PostgreSQLDataCustomConvertible.Type else {
/// FIXME: conditional conformance
throw PostgreSQLError(
identifier: "arrayElement",
reason: "`\(Self.self)` element `\(PostgreSQLArrayElement.self)` does not conform to `PostgreSQLDataCustomConvertible`"
)
}
for _ in 0..<metadata.count {
let count = Int(value.extract(Int32.self).bigEndian)
let subValue = value.extract(count: count)
let psqlData = PostgreSQLData(type: metadata.type, format: data.format, data: subValue)
let element = try type.convertFromPostgreSQLData(psqlData)
array.append(element as! PostgreSQLArrayElement)
}
} else {
array = []
}
return convertFromPostgreSQLArray(array)
}
/// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()`
public func convertToPostgreSQLData() throws -> PostgreSQLData {
let elements = try convertToPostgreSQLArray().map {
try ($0 as! PostgreSQLDataCustomConvertible).convertToPostgreSQLData()
}
guard let type = PostgreSQLArrayElement.self as? PostgreSQLDataCustomConvertible.Type else {
/// FIXME: conditional conformance
fatalError("PostgreSQLArrayCustomConvertible element type `\(PostgreSQLArrayElement.self)` does not conform to `PostgreSQLDataCustomConvertible`")
}
var data = Data()
data += Int32(1).data // non-null
data += Int32(0).data // b
data += type.postgreSQLDataType.raw.data // type
data += Int32(elements.count).data // length
data += Int32(1).data // dimensions
for element in elements {
if let value = element.data {
data += Int32(value.count).data
data += value
} else {
data += Int32(0).data
}
}
return PostgreSQLData(type: type.postgreSQLDataArrayType, format: .binary, data: data)
}
}
fileprivate struct PostgreSQLArrayMetadata {
/// 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(raw: _type.bigEndian, sql: "UNKNOWN")
}
/// The length of the array
var count: Int32 {
return _count.bigEndian
}
/// The number of dimensions
var dimensions: Int32 {
return _dimensions.bigEndian
}
}
extension PostgreSQLArrayMetadata: CustomStringConvertible {
/// See `CustomStringConvertible.description`
var description: String {
return "\(type)[\(count)]"
}
}
extension Array: PostgreSQLArrayCustomConvertible {
/// See `PostgreSQLArrayCustomConvertible.PostgreSQLArrayElement`
public typealias PostgreSQLArrayElement = Element
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
public static var postgreSQLDataType: PostgreSQLDataType {
guard let wrapped = Element.self as? PostgreSQLDataCustomConvertible.Type else {
/// FIXME: conditional conformance
fatalError("Array element type `\(Element.self)` does not conform to `PostgreSQLDataCustomConvertible`")
}
return wrapped.postgreSQLDataType
}
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
public static var postgreSQLDataArrayType: PostgreSQLDataType {
guard let wrapped = Element.self as? PostgreSQLDataCustomConvertible.Type else {
/// FIXME: conditional conformance
fatalError("Array element type `\(Element.self)` does not conform to `PostgreSQLDataCustomConvertible`")
}
return wrapped.postgreSQLDataArrayType
}
/// See `PostgreSQLArrayCustomConvertible.convertFromPostgreSQLArray(_:)`
public static func convertFromPostgreSQLArray(_ data: [Element]) -> Array<Element> {
return data
}
/// See `PostgreSQLArrayCustomConvertible.convertToPostgreSQLArray(_:)`
public func convertToPostgreSQLArray() -> [Element] {
return self
}
}