Skip to content

Commit 6dcd3bf

Browse files
committed
array support
1 parent 1fdc734 commit 6dcd3bf

15 files changed

+344
-85
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import Foundation
2+
3+
/// Representable by a `T[]` column on the PostgreSQL database.
4+
public protocol PostgreSQLArrayCustomConvertible: PostgreSQLDataCustomConvertible, Codable {
5+
/// The associated array element type
6+
associatedtype PostgreSQLArrayElement // :PostgreSQLDataCustomConvertible
7+
8+
/// Convert an array of elements to self.
9+
static func convertFromPostgreSQLArray(_ data: [PostgreSQLArrayElement]) -> Self
10+
11+
/// Convert self to an array of elements.
12+
func convertToPostgreSQLArray() -> [PostgreSQLArrayElement]
13+
}
14+
15+
extension PostgreSQLArrayCustomConvertible {
16+
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
17+
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Self {
18+
guard var value = data.data else {
19+
throw PostgreSQLError(identifier: "nullArray", reason: "Unable to decode PostgreSQL array from `null` data.")
20+
}
21+
22+
23+
/// Extract and convert each element.
24+
var array: [PostgreSQLArrayElement] = []
25+
26+
let hasData = value.extract(Int32.self).bigEndian
27+
if hasData == 1 {
28+
/// grab the array metadata from the beginning of the data
29+
let metadata = value.extract(PostgreSQLArrayMetadata.self)
30+
guard let type = PostgreSQLArrayElement.self as? PostgreSQLDataCustomConvertible.Type else {
31+
/// FIXME: conditional conformance
32+
throw PostgreSQLError(
33+
identifier: "arrayElement",
34+
reason: "`\(Self.self)` element `\(PostgreSQLArrayElement.self)` does not conform to `PostgreSQLDataCustomConvertible`"
35+
)
36+
}
37+
38+
for _ in 0..<metadata.count {
39+
let count = Int(value.extract(Int32.self).bigEndian)
40+
let subValue = value.extract(count: count)
41+
let psqlData = PostgreSQLData(type: metadata.type, format: data.format, data: subValue)
42+
let element = try type.convertFromPostgreSQLData(psqlData)
43+
array.append(element as! PostgreSQLArrayElement)
44+
}
45+
} else {
46+
array = []
47+
}
48+
49+
return convertFromPostgreSQLArray(array)
50+
}
51+
52+
/// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()`
53+
public func convertToPostgreSQLData() throws -> PostgreSQLData {
54+
let elements = try convertToPostgreSQLArray().map {
55+
try ($0 as! PostgreSQLDataCustomConvertible).convertToPostgreSQLData()
56+
}
57+
58+
guard let type = PostgreSQLArrayElement.self as? PostgreSQLDataCustomConvertible.Type else {
59+
/// FIXME: conditional conformance
60+
fatalError("PostgreSQLArrayCustomConvertible element type `\(PostgreSQLArrayElement.self)` does not conform to `PostgreSQLDataCustomConvertible`")
61+
}
62+
63+
var data = Data()
64+
data += Int32(1).data // non-null
65+
data += Int32(0).data // b
66+
data += type.postgreSQLDataType.raw.data // type
67+
data += Int32(elements.count).data // length
68+
data += Int32(1).data // dimensions
69+
70+
for element in elements {
71+
if let value = element.data {
72+
data += Int32(value.count).data
73+
data += value
74+
} else {
75+
data += Int32(0).data
76+
}
77+
}
78+
79+
80+
return PostgreSQLData(type: type.postgreSQLDataArrayType, format: .binary, data: data)
81+
}
82+
}
83+
84+
fileprivate struct PostgreSQLArrayMetadata {
85+
/// Unknown
86+
private let _b: Int32
87+
88+
/// The big-endian array element type
89+
private let _type: Int32
90+
91+
/// The big-endian length of the array
92+
private let _count: Int32
93+
94+
/// The big-endian number of dimensions
95+
private let _dimensions: Int32
96+
97+
/// Converts the raw array elemetn type to DataType
98+
var type: PostgreSQLDataType {
99+
return .init(raw: _type.bigEndian, sql: "UNKNOWN")
100+
}
101+
102+
/// The length of the array
103+
var count: Int32 {
104+
return _count.bigEndian
105+
}
106+
107+
/// The number of dimensions
108+
var dimensions: Int32 {
109+
return _dimensions.bigEndian
110+
}
111+
}
112+
113+
extension PostgreSQLArrayMetadata: CustomStringConvertible {
114+
/// See `CustomStringConvertible.description`
115+
var description: String {
116+
return "\(type)[\(count)]"
117+
}
118+
}
119+
120+
extension Array: PostgreSQLArrayCustomConvertible {
121+
/// See `PostgreSQLArrayCustomConvertible.PostgreSQLArrayElement`
122+
public typealias PostgreSQLArrayElement = Element
123+
124+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
125+
public static var postgreSQLDataType: PostgreSQLDataType {
126+
guard let wrapped = Element.self as? PostgreSQLDataCustomConvertible.Type else {
127+
/// FIXME: conditional conformance
128+
fatalError("Array element type `\(Element.self)` does not conform to `PostgreSQLDataCustomConvertible`")
129+
}
130+
return wrapped.postgreSQLDataType
131+
}
132+
133+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
134+
public static var postgreSQLDataArrayType: PostgreSQLDataType {
135+
guard let wrapped = Element.self as? PostgreSQLDataCustomConvertible.Type else {
136+
/// FIXME: conditional conformance
137+
fatalError("Array element type `\(Element.self)` does not conform to `PostgreSQLDataCustomConvertible`")
138+
}
139+
return wrapped.postgreSQLDataArrayType
140+
}
141+
142+
/// See `PostgreSQLArrayCustomConvertible.convertFromPostgreSQLArray(_:)`
143+
public static func convertFromPostgreSQLArray(_ data: [Element]) -> Array<Element> {
144+
return data
145+
}
146+
147+
/// See `PostgreSQLArrayCustomConvertible.convertToPostgreSQLArray(_:)`
148+
public func convertToPostgreSQLArray() -> [Element] {
149+
return self
150+
}
151+
}

Sources/PostgreSQL/Data/PostgreSQLData+BinaryFloatingPoint.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@ extension BinaryFloatingPoint {
66
return exponentBitCount + significandBitCount + 1
77
}
88

9+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
10+
public static var postgreSQLDataType: PostgreSQLDataType {
11+
switch Self.bitWidth {
12+
case 32: return .float4
13+
case 64: return .float8
14+
default: fatalError("Unsupported floating point bit width: \(Self.bitWidth)")
15+
}
16+
}
17+
18+
19+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
20+
public static var postgreSQLDataArrayType: PostgreSQLDataType {
21+
switch Self.bitWidth {
22+
case 32: return ._float4
23+
case 64: return ._float8
24+
default: fatalError("Unsupported floating point bit width: \(Self.bitWidth)")
25+
}
26+
}
27+
928
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
1029
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Self {
1130
guard let value = data.data else {
@@ -40,16 +59,7 @@ extension BinaryFloatingPoint {
4059

4160
/// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()`
4261
public func convertToPostgreSQLData() throws -> PostgreSQLData {
43-
let type: PostgreSQLDataType
44-
switch Self.bitWidth {
45-
case 32: type = .float4
46-
case 64: type = .float8
47-
default: throw PostgreSQLError(
48-
identifier: "floatingPointBitWidth",
49-
reason: "Unsupported floating point bit width: \(Self.bitWidth)"
50-
)
51-
}
52-
return PostgreSQLData(type: type, format: .binary, data: data)
62+
return PostgreSQLData(type: Self.postgreSQLDataType, format: .binary, data: data)
5363
}
5464
}
5565

Sources/PostgreSQL/Data/PostgreSQLData+Bool.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import Foundation
22

33
extension Bool: PostgreSQLDataCustomConvertible {
4+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
5+
public static var postgreSQLDataType: PostgreSQLDataType { return .bool }
6+
7+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
8+
public static var postgreSQLDataArrayType: PostgreSQLDataType { return ._bool }
9+
410
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
511
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Bool {
612
guard let value = data.data else {

Sources/PostgreSQL/Data/PostgreSQLData+Data.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import Foundation
22

33
extension Data: PostgreSQLDataCustomConvertible {
4+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
5+
public static var postgreSQLDataType: PostgreSQLDataType { return .bytea }
6+
7+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
8+
public static var postgreSQLDataArrayType: PostgreSQLDataType { return .bytea }
9+
410
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
511
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Data {
612
guard let value = data.data else {

Sources/PostgreSQL/Data/PostgreSQLData+Date.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import Foundation
22

33
extension Date: PostgreSQLDataCustomConvertible {
4+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
5+
public static var postgreSQLDataType: PostgreSQLDataType { return .timestamp }
6+
7+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
8+
public static var postgreSQLDataArrayType: PostgreSQLDataType { return ._timestamp }
9+
410
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
511
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Date {
612
guard let value = data.data else {

Sources/PostgreSQL/Data/PostgreSQLData+FixedWidthInteger.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
import Foundation
22

33
extension FixedWidthInteger {
4+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
5+
public static var postgreSQLDataType: PostgreSQLDataType {
6+
switch Self.bitWidth {
7+
case 8: return .char
8+
case 16: return .int2
9+
case 32: return .int4
10+
case 64: return .int8
11+
default: fatalError("Integer bit width not supported: \(Self.bitWidth)")
12+
}
13+
}
14+
15+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
16+
public static var postgreSQLDataArrayType: PostgreSQLDataType {
17+
switch Self.bitWidth {
18+
case 8: return ._char
19+
case 16: return ._int2
20+
case 32: return ._int4
21+
case 64: return ._int8
22+
default: fatalError("Integer bit width not supported: \(Self.bitWidth)")
23+
}
24+
}
25+
426
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
527
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Self {
628
guard let value = data.data else {
@@ -26,15 +48,7 @@ extension FixedWidthInteger {
2648

2749
/// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()`
2850
public func convertToPostgreSQLData() throws -> PostgreSQLData {
29-
let type: PostgreSQLDataType
30-
switch Self.bitWidth {
31-
case 8: type = .char
32-
case 16: type = .int2
33-
case 32: type = .int4
34-
case 64: type = .int8
35-
default: throw DecodingError.typeMismatch(Self.self, .init(codingPath: [], debugDescription: "Integer bit width not supported: \(Self.bitWidth)"))
36-
}
37-
return PostgreSQLData(type: type, format: .binary, data: self.data)
51+
return PostgreSQLData(type: Self.postgreSQLDataType, format: .binary, data: self.data)
3852
}
3953

4054

Sources/PostgreSQL/Data/PostgreSQLData+Optional.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,22 @@ extension OptionalType {
2626
}
2727
}
2828

29-
extension Optional: PostgreSQLDataCustomConvertible { }
29+
extension Optional: PostgreSQLDataCustomConvertible {
30+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
31+
public static var postgreSQLDataType: PostgreSQLDataType {
32+
guard let wrapped = Wrapped.self as? PostgreSQLDataCustomConvertible.Type else {
33+
fatalError("Optional wrapped type `\(Wrapped.self)` does not conform to `PostgreSQLDataCustomConvertible`")
34+
}
35+
return wrapped.postgreSQLDataType
36+
}
37+
38+
39+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
40+
public static var postgreSQLDataArrayType: PostgreSQLDataType {
41+
guard let wrapped = Wrapped.self as? PostgreSQLDataCustomConvertible.Type else {
42+
fatalError("Optional wrapped type `\(Wrapped.self)` does not conform to `PostgreSQLDataCustomConvertible`")
43+
}
44+
return wrapped.postgreSQLDataArrayType
45+
}
46+
}
3047

Sources/PostgreSQL/Data/PostgreSQLData+Point.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import Foundation
33
/// A 2-dimenstional (double[2]) point.
44
public struct PostgreSQLPoint {
55
/// The point's x coordinate.
6-
var x: Double
6+
public var x: Double
77

88
/// The point's y coordinate.
9-
var y: Double
9+
public var y: Double
1010

1111
/// Create a new `Point`
1212
public init(x: Double, y: Double) {
@@ -30,6 +30,12 @@ extension PostgreSQLPoint: Equatable {
3030
}
3131

3232
extension PostgreSQLPoint: PostgreSQLDataCustomConvertible {
33+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
34+
public static var postgreSQLDataType: PostgreSQLDataType { return .point }
35+
36+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
37+
public static var postgreSQLDataArrayType: PostgreSQLDataType { return ._point }
38+
3339
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
3440
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> PostgreSQLPoint {
3541
guard let value = data.data else {

Sources/PostgreSQL/Data/PostgreSQLData+String.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import Foundation
22

33
extension String: PostgreSQLDataCustomConvertible {
4+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
5+
public static var postgreSQLDataType: PostgreSQLDataType { return .text }
6+
7+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
8+
public static var postgreSQLDataArrayType: PostgreSQLDataType { return ._text }
9+
410
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
511
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> String {
612
guard let value = data.data else {

Sources/PostgreSQL/Data/PostgreSQLData+UUID.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import Foundation
22

33
extension UUID: PostgreSQLDataCustomConvertible {
4+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
5+
public static var postgreSQLDataType: PostgreSQLDataType { return .uuid }
6+
7+
8+
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
9+
public static var postgreSQLDataArrayType: PostgreSQLDataType { return ._uuid }
10+
411
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
512
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> UUID {
613
guard let value = data.data else {

0 commit comments

Comments
 (0)