Skip to content

Commit 830abbc

Browse files
ericchapmantanner0101
authored andcommitted
added polygon data-type (#129)
* added polygon data-type * added polygon data-type * added tests for polygon * fixed polygon typos
1 parent 4388d3c commit 830abbc

File tree

4 files changed

+120
-1
lines changed

4 files changed

+120
-1
lines changed

Sources/PostgreSQL/Column/PostgreSQLDataTypeCode.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public struct PostgreSQLDataFormat: Codable, Equatable, ExpressibleByIntegerLite
2929
public static let pg_node_tree = PostgreSQLDataFormat(194)
3030
/// `600`
3131
public static let point = PostgreSQLDataFormat(600)
32+
/// `604`
33+
public static let polygon = PostgreSQLDataFormat(604)
3234
/// `700`
3335
public static let float4 = PostgreSQLDataFormat(700)
3436
/// `701`
@@ -123,6 +125,7 @@ extension PostgreSQLDataFormat {
123125
case .json: return "JSON"
124126
case .pg_node_tree: return "PGNODETREE"
125127
case .point: return "POINT"
128+
case .polygon: return "POLYGON"
126129
case .float4: return "REAL"
127130
case .float8: return "DOUBLE PRECISION"
128131
case ._bool: return "BOOLEAN[]"
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import Foundation
2+
3+
/// A 2-dimensional list of (double[2]) points representing a polygon.
4+
public struct PostgreSQLPolygon: Codable, Equatable {
5+
/// The points that make up the polygon.
6+
public var points: [PostgreSQLPoint]
7+
8+
/// Create a new `Polygon`
9+
public init(points: [PostgreSQLPoint]) {
10+
self.points = points
11+
}
12+
}
13+
14+
extension PostgreSQLPolygon: CustomStringConvertible {
15+
/// See `CustomStringConvertible`.
16+
public var description: String {
17+
return "(\(self.points.map{ $0.description }.joined(separator: ",")))"
18+
}
19+
}
20+
21+
extension PostgreSQLPolygon: PostgreSQLDataConvertible {
22+
/// See `PostgreSQLDataConvertible`.
23+
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> PostgreSQLPolygon {
24+
guard case .polygon = data.type else {
25+
throw PostgreSQLError.decode(self, from: data)
26+
}
27+
switch data.storage {
28+
case .text(let string):
29+
var points = [PostgreSQLPoint]()
30+
var count = 0
31+
32+
let parts = string.split(separator: ",")
33+
while count < parts.count {
34+
var x = parts[count]
35+
var y = parts[count+1]
36+
37+
// Check initial "("
38+
if count == 0 { assert(x.popFirst() == "(") }
39+
40+
count += 2
41+
42+
// Check end ")"
43+
if count == parts.count { assert(y.popLast() == ")") }
44+
45+
// Check Normal "(" and ")"
46+
assert(x.popFirst() == "(")
47+
assert(y.popLast() == ")")
48+
49+
// Create the point
50+
points.append(PostgreSQLPoint(x: Double(x)!, y: Double(y)!))
51+
}
52+
return .init(points: points)
53+
case .binary(let value):
54+
let total = value[0..<4].as(UInt32.self, default: 0).bigEndian
55+
assert(total == (value.count-4)/16)
56+
57+
var points = [PostgreSQLPoint]()
58+
var count = 4
59+
while count < value.count {
60+
let x = Data(value[count..<count+8].reversed())
61+
let y = Data(value[count+8..<count+16].reversed())
62+
points.append(PostgreSQLPoint(x: x.as(Double.self, default: 0), y: y.as(Double.self, default: 0)))
63+
count += 16
64+
}
65+
66+
return .init(points: points)
67+
68+
case .null: throw PostgreSQLError.decode(self, from: data)
69+
}
70+
}
71+
72+
/// See `PostgreSQLDataConvertible`.
73+
public func convertToPostgreSQLData() throws -> PostgreSQLData {
74+
var data = Data.of(Int32(self.points.count).bigEndian)
75+
for point in self.points {
76+
data += Data.of(point.x).reversed()
77+
data += Data.of(point.y).reversed()
78+
}
79+
return PostgreSQLData(.polygon, binary: data)
80+
}
81+
}

Sources/PostgreSQL/SQL/PostgreSQLDataTypeStaticRepresentable.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,13 @@ extension PostgreSQLPoint: PostgreSQLDataTypeStaticRepresentable, ReflectionDeco
106106
return (.init(x: 0, y: 0), .init(x: 1, y: 1))
107107
}
108108
}
109+
110+
extension PostgreSQLPolygon: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable {
111+
/// See `PostgreSQLDataTypeStaticRepresentable`.
112+
public static var postgreSQLDataType: PostgreSQLDataType { return .polygon }
113+
114+
/// See `ReflectionDecodable`.
115+
public static func reflectDecoded() throws -> (PostgreSQLPolygon, PostgreSQLPolygon) {
116+
return (.init(points: [PostgreSQLPoint(x: 0, y: 0)]), .init(points: [PostgreSQLPoint(x: 1, y: 1)]))
117+
}
118+
}

Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ class PostgreSQLConnectionTests: XCTestCase {
361361
var date: Date
362362
var decimal: Decimal
363363
var point: PostgreSQLPoint
364+
var polygon: PostgreSQLPolygon
364365
}
365366

366367
defer {
@@ -385,6 +386,7 @@ class PostgreSQLConnectionTests: XCTestCase {
385386
.column(for: \Types.date)
386387
.column(for: \Types.decimal)
387388
.column(for: \Types.point)
389+
.column(for: \Types.polygon)
388390
.run().wait()
389391

390392
let typesA = Types(
@@ -405,7 +407,8 @@ class PostgreSQLConnectionTests: XCTestCase {
405407
float: 3.14,
406408
date: Date(),
407409
decimal: .init(-1.234),
408-
point: .init(x: 1.570, y: -42)
410+
point: .init(x: 1.570, y: -42),
411+
polygon: .init(points: [PostgreSQLPoint(x: 100, y: 100), PostgreSQLPoint(x: 200, y: 100), PostgreSQLPoint(x: 200, y: 200), PostgreSQLPoint(x: 100, y: 200)])
409412
)
410413
try conn.insert(into: Types.self).value(typesA).run().wait()
411414
let rows = try conn.select().all().from(Types.self).all(decoding: Types.self).wait()
@@ -429,6 +432,7 @@ class PostgreSQLConnectionTests: XCTestCase {
429432
XCTAssertEqual(typesA.date, typesB.date)
430433
XCTAssertEqual(typesA.decimal, typesB.decimal)
431434
XCTAssertEqual(typesA.point, typesB.point)
435+
XCTAssertEqual(typesA.polygon, typesB.polygon)
432436
default: XCTFail("Invalid row count")
433437
}
434438
}
@@ -665,6 +669,26 @@ class PostgreSQLConnectionTests: XCTestCase {
665669
print(x)
666670
}
667671

672+
func testPolygon() throws {
673+
let conn = try PostgreSQLConnection.makeTest()
674+
let decoder = PostgreSQLRowDecoder()
675+
struct Test: Codable {
676+
var polygon: PostgreSQLPolygon
677+
}
678+
try conn.query("SELECT '((100,100),(200,100),(200,200),(100,200))'::POLYGON as polygon") { row in
679+
let polygon = try decoder.decode(Test.self, from: row)
680+
XCTAssertEqual(polygon.polygon.points.count, 4)
681+
XCTAssertEqual(polygon.polygon.points[0].x, 100)
682+
XCTAssertEqual(polygon.polygon.points[0].y, 100)
683+
XCTAssertEqual(polygon.polygon.points[1].x, 200)
684+
XCTAssertEqual(polygon.polygon.points[1].y, 100)
685+
XCTAssertEqual(polygon.polygon.points[2].x, 200)
686+
XCTAssertEqual(polygon.polygon.points[2].y, 200)
687+
XCTAssertEqual(polygon.polygon.points[3].x, 100)
688+
XCTAssertEqual(polygon.polygon.points[3].y, 200)
689+
}.wait()
690+
}
691+
668692
static var allTests = [
669693
("testBenchmark", testBenchmark),
670694
("testVersion", testVersion),
@@ -689,6 +713,7 @@ class PostgreSQLConnectionTests: XCTestCase {
689713
("testNumericDecode", testNumericDecode),
690714
("testClosureRetainCycle", testClosureRetainCycle),
691715
("testGH125", testGH125),
716+
("testPolygon", testPolygon),
692717
]
693718
}
694719

0 commit comments

Comments
 (0)