Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added polygon data-type #129

Merged
merged 5 commits into from
Mar 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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