diff --git a/Sources/PostgreSQL/Column/PostgreSQLDataTypeCode.swift b/Sources/PostgreSQL/Column/PostgreSQLDataTypeCode.swift index 6918c2ed..7e479d92 100644 --- a/Sources/PostgreSQL/Column/PostgreSQLDataTypeCode.swift +++ b/Sources/PostgreSQL/Column/PostgreSQLDataTypeCode.swift @@ -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` @@ -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[]" diff --git a/Sources/PostgreSQL/Data/PostgreSQLData+Polygon.swift b/Sources/PostgreSQL/Data/PostgreSQLData+Polygon.swift new file mode 100644 index 00000000..9dc137b1 --- /dev/null +++ b/Sources/PostgreSQL/Data/PostgreSQLData+Polygon.swift @@ -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.. 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) + } +} diff --git a/Sources/PostgreSQL/SQL/PostgreSQLDataTypeStaticRepresentable.swift b/Sources/PostgreSQL/SQL/PostgreSQLDataTypeStaticRepresentable.swift index 5e762c56..5ecf59a6 100644 --- a/Sources/PostgreSQL/SQL/PostgreSQLDataTypeStaticRepresentable.swift +++ b/Sources/PostgreSQL/SQL/PostgreSQLDataTypeStaticRepresentable.swift @@ -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)])) + } +} diff --git a/Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift b/Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift index 43e8d2f8..baae5658 100644 --- a/Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift +++ b/Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift @@ -361,6 +361,7 @@ class PostgreSQLConnectionTests: XCTestCase { var date: Date var decimal: Decimal var point: PostgreSQLPoint + var polygon: PostgreSQLPolygon } defer { @@ -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( @@ -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() @@ -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") } } @@ -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), @@ -689,6 +713,7 @@ class PostgreSQLConnectionTests: XCTestCase { ("testNumericDecode", testNumericDecode), ("testClosureRetainCycle", testClosureRetainCycle), ("testGH125", testGH125), + ("testPolygon", testPolygon), ] }