Skip to content

Commit

Permalink
added polygon data-type (#129)
Browse files Browse the repository at this point in the history
* added polygon data-type

* added polygon data-type

* added tests for polygon

* fixed polygon typos
  • Loading branch information
ericchapman authored and tanner0101 committed Mar 1, 2019
1 parent 4388d3c commit 830abbc
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Sources/PostgreSQL/Column/PostgreSQLDataTypeCode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public struct PostgreSQLDataFormat: Codable, Equatable, ExpressibleByIntegerLite
public static let pg_node_tree = PostgreSQLDataFormat(194)
/// `600`
public static let point = PostgreSQLDataFormat(600)
/// `604`
public static let polygon = PostgreSQLDataFormat(604)
/// `700`
public static let float4 = PostgreSQLDataFormat(700)
/// `701`
Expand Down Expand Up @@ -123,6 +125,7 @@ extension PostgreSQLDataFormat {
case .json: return "JSON"
case .pg_node_tree: return "PGNODETREE"
case .point: return "POINT"
case .polygon: return "POLYGON"
case .float4: return "REAL"
case .float8: return "DOUBLE PRECISION"
case ._bool: return "BOOLEAN[]"
Expand Down
81 changes: 81 additions & 0 deletions Sources/PostgreSQL/Data/PostgreSQLData+Polygon.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Foundation

/// A 2-dimensional list of (double[2]) points representing a polygon.
public struct PostgreSQLPolygon: Codable, Equatable {
/// The points that make up the polygon.
public var points: [PostgreSQLPoint]

/// Create a new `Polygon`
public init(points: [PostgreSQLPoint]) {
self.points = points
}
}

extension PostgreSQLPolygon: CustomStringConvertible {
/// See `CustomStringConvertible`.
public var description: String {
return "(\(self.points.map{ $0.description }.joined(separator: ",")))"
}
}

extension PostgreSQLPolygon: PostgreSQLDataConvertible {
/// See `PostgreSQLDataConvertible`.
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> PostgreSQLPolygon {
guard case .polygon = data.type else {
throw PostgreSQLError.decode(self, from: data)
}
switch data.storage {
case .text(let string):
var points = [PostgreSQLPoint]()
var count = 0

let parts = string.split(separator: ",")
while count < parts.count {
var x = parts[count]
var y = parts[count+1]

// Check initial "("
if count == 0 { assert(x.popFirst() == "(") }

count += 2

// Check end ")"
if count == parts.count { assert(y.popLast() == ")") }

// Check Normal "(" and ")"
assert(x.popFirst() == "(")
assert(y.popLast() == ")")

// Create the point
points.append(PostgreSQLPoint(x: Double(x)!, y: Double(y)!))
}
return .init(points: points)
case .binary(let value):
let total = value[0..<4].as(UInt32.self, default: 0).bigEndian
assert(total == (value.count-4)/16)

var points = [PostgreSQLPoint]()
var count = 4
while count < value.count {
let x = Data(value[count..<count+8].reversed())
let y = Data(value[count+8..<count+16].reversed())
points.append(PostgreSQLPoint(x: x.as(Double.self, default: 0), y: y.as(Double.self, default: 0)))
count += 16
}

return .init(points: points)

case .null: throw PostgreSQLError.decode(self, from: data)
}
}

/// See `PostgreSQLDataConvertible`.
public func convertToPostgreSQLData() throws -> PostgreSQLData {
var data = Data.of(Int32(self.points.count).bigEndian)
for point in self.points {
data += Data.of(point.x).reversed()
data += Data.of(point.y).reversed()
}
return PostgreSQLData(.polygon, binary: data)
}
}
10 changes: 10 additions & 0 deletions Sources/PostgreSQL/SQL/PostgreSQLDataTypeStaticRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,13 @@ extension PostgreSQLPoint: PostgreSQLDataTypeStaticRepresentable, ReflectionDeco
return (.init(x: 0, y: 0), .init(x: 1, y: 1))
}
}

extension PostgreSQLPolygon: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable {
/// See `PostgreSQLDataTypeStaticRepresentable`.
public static var postgreSQLDataType: PostgreSQLDataType { return .polygon }

/// See `ReflectionDecodable`.
public static func reflectDecoded() throws -> (PostgreSQLPolygon, PostgreSQLPolygon) {
return (.init(points: [PostgreSQLPoint(x: 0, y: 0)]), .init(points: [PostgreSQLPoint(x: 1, y: 1)]))
}
}
27 changes: 26 additions & 1 deletion Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ class PostgreSQLConnectionTests: XCTestCase {
var date: Date
var decimal: Decimal
var point: PostgreSQLPoint
var polygon: PostgreSQLPolygon
}

defer {
Expand All @@ -385,6 +386,7 @@ class PostgreSQLConnectionTests: XCTestCase {
.column(for: \Types.date)
.column(for: \Types.decimal)
.column(for: \Types.point)
.column(for: \Types.polygon)
.run().wait()

let typesA = Types(
Expand All @@ -405,7 +407,8 @@ class PostgreSQLConnectionTests: XCTestCase {
float: 3.14,
date: Date(),
decimal: .init(-1.234),
point: .init(x: 1.570, y: -42)
point: .init(x: 1.570, y: -42),
polygon: .init(points: [PostgreSQLPoint(x: 100, y: 100), PostgreSQLPoint(x: 200, y: 100), PostgreSQLPoint(x: 200, y: 200), PostgreSQLPoint(x: 100, y: 200)])
)
try conn.insert(into: Types.self).value(typesA).run().wait()
let rows = try conn.select().all().from(Types.self).all(decoding: Types.self).wait()
Expand All @@ -429,6 +432,7 @@ class PostgreSQLConnectionTests: XCTestCase {
XCTAssertEqual(typesA.date, typesB.date)
XCTAssertEqual(typesA.decimal, typesB.decimal)
XCTAssertEqual(typesA.point, typesB.point)
XCTAssertEqual(typesA.polygon, typesB.polygon)
default: XCTFail("Invalid row count")
}
}
Expand Down Expand Up @@ -665,6 +669,26 @@ class PostgreSQLConnectionTests: XCTestCase {
print(x)
}

func testPolygon() throws {
let conn = try PostgreSQLConnection.makeTest()
let decoder = PostgreSQLRowDecoder()
struct Test: Codable {
var polygon: PostgreSQLPolygon
}
try conn.query("SELECT '((100,100),(200,100),(200,200),(100,200))'::POLYGON as polygon") { row in
let polygon = try decoder.decode(Test.self, from: row)
XCTAssertEqual(polygon.polygon.points.count, 4)
XCTAssertEqual(polygon.polygon.points[0].x, 100)
XCTAssertEqual(polygon.polygon.points[0].y, 100)
XCTAssertEqual(polygon.polygon.points[1].x, 200)
XCTAssertEqual(polygon.polygon.points[1].y, 100)
XCTAssertEqual(polygon.polygon.points[2].x, 200)
XCTAssertEqual(polygon.polygon.points[2].y, 200)
XCTAssertEqual(polygon.polygon.points[3].x, 100)
XCTAssertEqual(polygon.polygon.points[3].y, 200)
}.wait()
}

static var allTests = [
("testBenchmark", testBenchmark),
("testVersion", testVersion),
Expand All @@ -689,6 +713,7 @@ class PostgreSQLConnectionTests: XCTestCase {
("testNumericDecode", testNumericDecode),
("testClosureRetainCycle", testClosureRetainCycle),
("testGH125", testGH125),
("testPolygon", testPolygon),
]
}

Expand Down

0 comments on commit 830abbc

Please sign in to comment.