diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c56d059 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI +on: +- push +- pull_request +jobs: + test: + name: Run tests + runs-on: ubuntu-latest + services: + postgres: + image: postgis/postgis + ports: + - "5432:5432" + env: + POSTGRES_USER: fluentpostgis + POSTGRES_PASSWORD: fluentpostgis + POSTGRES_DB: postgis_tests + steps: + - uses: actions/checkout@v3 + - run: swift test diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..b7c1330 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,8 @@ +--extensionacl on-declarations +--header strip +--maxwidth 100 +--self insert +--swiftversion 5.5 +--wraparguments before-first +--wrapcollections before-first +--wrapparameters before-first diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2c988d1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -env: - global: - - SWIFT_BRANCH=swift-4.2-release - - SWIFT_VERSION=swift-4.2-RELEASE -matrix: - include: - - os: linux - language: generic - dist: trusty - sudo: required - addons: - postgresql: 9.6 - apt: - packages: - - postgresql-9.6-postgis-2.3 - install: - - sudo apt-get install clang libicu-dev - - mkdir swift - - curl https://swift.org/builds/$SWIFT_BRANCH/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz -s | tar xz -C swift &> /dev/null - - export PATH="$(pwd)/swift/$SWIFT_VERSION-ubuntu14.04/usr/bin:$PATH" - before_script: - - psql -c 'create database postgis_tests;' -U postgres - script: - - swift package update - - swift test - - - os: osx - osx_image: xcode10 - language: swift - sudo: required - install: - - brew install vapor/tap/vapor - - wget https://swift.org/builds/$SWIFT_BRANCH/xcode/$SWIFT_VERSION/$SWIFT_VERSION-osx.pkg - - sudo installer -pkg $SWIFT_VERSION-osx.pkg -target / - - export PATH="/Library/Developer/Toolchains/$SWIFT_VERSION.xctoolchain/usr/bin:$PATH" - before_script: - - export PG_DATA=$(brew --prefix)/var/postgres - - pg_ctl -w start -l postgres.log --pgdata ${PG_DATA} - - createuser -s postgres - - psql -c 'create database postgis_tests;' -U postgres - - cat postgres.log - script: - - swift package update - - swift test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5c8e320 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test-db: + docker run --rm -e POSTGRES_PASSWORD=fluentpostgis -e POSTGRES_USER=fluentpostgis -e POSTGRES_DB=postgis_tests -p 5432:5432 odidev/postgis:11-2.5-alpine diff --git a/Package.swift b/Package.swift index ddad733..7799b48 100644 --- a/Package.swift +++ b/Package.swift @@ -1,30 +1,37 @@ -// swift-tools-version:4.1 +// swift-tools-version:5.5 import PackageDescription let package = Package( - name: "FluentPostGIS", + name: "fluent-postgis", + platforms: [ + .macOS(.v12), + ], products: [ - // FluentPostgreSQL support for PostGIS .library( name: "FluentPostGIS", - targets: ["FluentPostGIS"]), + targets: ["FluentPostGIS"] + ), ], dependencies: [ - // Swift ORM framework (queries, models, and relations) for building NoSQL and SQL database integrations. - .package(url: "https://github.com/vapor/fluent.git", from: "3.0.0"), - - // 🐘 Non-blocking, event-driven Swift client for PostgreSQL. - .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0"), - - // Well Known Binary Encoding and Decoding - .package(url: "https://github.com/plarson/WKCodable", from: "0.1.1"), + .package(url: "https://github.com/vapor/fluent-kit.git", from: "1.0.0"), + .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"), + .package(url: "https://github.com/rabc/WKCodable.git", from: "0.1.0"), ], targets: [ .target( name: "FluentPostGIS", - dependencies: ["FluentPostgreSQL", "WKCodable"]), + dependencies: [ + .product(name: "FluentKit", package: "fluent-kit"), + .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), + .product(name: "WKCodable", package: "WKCodable"), + ] + ), .testTarget( name: "FluentPostGISTests", - dependencies: ["FluentBenchmark", "FluentPostGIS"]), + dependencies: [ + .target(name: "FluentPostGIS"), + .product(name: "FluentBenchmark", package: "fluent-kit"), + ] + ), ] ) diff --git a/README.md b/README.md index ea37da1..f8d1a89 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,73 @@ # FluentPostGIS -[![Build Status](https://travis-ci.org/plarson/fluent-postgis.svg?branch=master)](https://travis-ci.org/plarson/fluent-postgis) ![Platforms](https://img.shields.io/badge/platforms-Linux%20%7C%20OS%20X-blue.svg) ![Package Managers](https://img.shields.io/badge/package%20managers-SwiftPM-yellow.svg) -[![Twitter dizm](https://img.shields.io/badge/twitter-dizm-green.svg)](http://twitter.com/dizm) -PostGIS support for [FluentPostgreSQL](https://github.com/vapor/fluent-postgresql) and [Vapor](https://github.com/vapor/vapor) +A fork of the [FluentPostGIS](https://github.com/plarson/fluent-postgis) package which adds support for geographic queries. FluentPostGIS provides PostGIS support for [fluent-postgres-driver](https://github.com/vapor/fluent-postgres-driver) and [Vapor 4](https://github.com/vapor/vapor). # Installation ## Swift Package Manager +Add this line to your dependencies in `Package.swift`: + +```swift +.package(url: "https://github.com/brokenhandsio/fluent-postgis.git", from: "0.3.0") +``` + +Then add this line to a target's dependencies: + ```swift -.package(url: "https://github.com/plarson/fluent-postgis.git", .branch("master")) +.product(name: "FluentPostGIS", package: "fluent-postgis"), ``` + # Setup + Import module + ```swift import FluentPostGIS ``` -Add to ```configure.swift``` +Optionally, you can add a `Migration` to enable PostGIS: + ```swift -try services.register(FluentPostGISProvider()) +app.migrations.add(EnablePostGISMigration()) + ``` + # Models -Add ```GISGeographicPoint2D``` to your models + +Add a type to your model + ```swift -final class User: PostgreSQLModel { - var id: Int? - var name: String - var location: GISGeographicPoint2D? +final class User: Model { + static let schema = "user" + + @ID(key: .id) + var id: UUID? + + @Field(key: "location") + var location: GeometricPoint2D } ``` + +Then use its data type in the `Migration`: + +```swift +struct UserMigration: AsyncMigration { + func prepare(on database: Database) async throws -> { + try await database.schema(User.schema) + .id() + .field("location", .geometricPoint2D) + .create() + } + func revert(on database: Database) async throws -> { + try await database.schema(User.schema).delete() + } +} +``` + | Geometric Types | Geographic Types | |---|---| |GeometricPoint2D|GeographicPoint2D| @@ -44,10 +79,14 @@ final class User: PostgreSQLModel { |GeometricGeometryCollection2D|GeographicGeometryCollection2D| # Queries -Query locations using ```ST_DWithin``` -```swift -let searchLocation = GISGeographicPoint2D(longitude: -71.060316, latitude: 48.432044) -try User.query(on: conn).filterGeometryDistanceWithin(\User.location, searchLocation, 1000).all().wait() + +Query using any of the filter functions: + +```swift +let eiffelTower = GeographicPoint2D(longitude: 2.2945, latitude: 48.858222) +try await User.query(on: database) + .filterGeographyDistanceWithin(\.$location, eiffelTower, 1000) + .all() ``` | Queries | @@ -57,11 +96,13 @@ try User.query(on: conn).filterGeometryDistanceWithin(\User.location, searchLoca |filterGeometryDisjoint| |filterGeometryDistance| |filterGeometryDistanceWithin| +|filterGeographyDistanceWithin| |filterGeometryEquals| |filterGeometryIntersects| |filterGeometryOverlaps| |filterGeometryTouches| |filterGeometryWithin| +|sortByDistance| :gift_heart: Contributing ------------ @@ -73,4 +114,6 @@ MIT :alien: Author ------ +BrokenHands, Tim Condon, Nikolai Guyot - https://www.brokenhands.io/ +Ricardo Carvalho - https://rabc.github.io/ Phil Larson - http://dizm.com diff --git a/Sources/FluentPostGIS/Geographic/GeographicLineString2D.swift b/Sources/FluentPostGIS/Geographic/GeographicLineString2D.swift deleted file mode 100644 index 7e7e67a..0000000 --- a/Sources/FluentPostGIS/Geographic/GeographicLineString2D.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation -import PostgreSQL -import WKCodable - -public struct GeographicLineString2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { - /// The points - public var points: [GeographicPoint2D] - - /// Create a new `GISGeographicLineString2D` - public init(points: [GeographicPoint2D]) { - self.points = points - } -} - -extension GeographicLineString2D: GeometryConvertible, GeometryCollectable { - /// Convertible type - public typealias GeometryType = LineString - - public init(geometry lineString: GeometryType) { - let points = lineString.points.map { GeographicPoint2D(geometry: $0) } - self.init(points: points) - } - - public var geometry: GeometryType { - return .init(points: self.points.map { $0.geometry }, srid: FluentPostGISSrid) - } - - public var baseGeometry: Geometry { - return self.geometry - } -} - -extension GeographicLineString2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geographicLineString } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeographicLineString2D, GeographicLineString2D) { - return (.init(points: []), .init(points: [GeographicPoint2D(longitude: 0, latitude: 0)])) - } -} diff --git a/Sources/FluentPostGIS/Geographic/GeographicMultiPoint2D.swift b/Sources/FluentPostGIS/Geographic/GeographicMultiPoint2D.swift deleted file mode 100644 index 97f50a3..0000000 --- a/Sources/FluentPostGIS/Geographic/GeographicMultiPoint2D.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation -import PostgreSQL -import WKCodable - -public struct GeographicMultiPoint2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { - /// The points - public var points: [GeographicPoint2D] - - /// Create a new `GISGeographicLineString2D` - public init(points: [GeographicPoint2D]) { - self.points = points - } -} - -extension GeographicMultiPoint2D: GeometryConvertible, GeometryCollectable { - /// Convertible type - public typealias GeometryType = MultiPoint - - public init(geometry lineString: GeometryType) { - let points = lineString.points.map { GeographicPoint2D(geometry: $0) } - self.init(points: points) - } - - public var geometry: GeometryType { - return .init(points: self.points.map { $0.geometry }, srid: FluentPostGISSrid) - } - - public var baseGeometry: Geometry { - return self.geometry - } -} - -extension GeographicMultiPoint2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geographicMultiPoint } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeographicMultiPoint2D, GeographicMultiPoint2D) { - return (.init(points: []), .init(points: [GeographicPoint2D(longitude: 0, latitude: 0)])) - } -} diff --git a/Sources/FluentPostGIS/Geographic/GeographicMultiPolygon2D.swift b/Sources/FluentPostGIS/Geographic/GeographicMultiPolygon2D.swift deleted file mode 100644 index f0f552f..0000000 --- a/Sources/FluentPostGIS/Geographic/GeographicMultiPolygon2D.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation -import PostgreSQL -import WKCodable - -public struct GeographicMultiPolygon2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { - /// The points - public let polygons: [GeographicPolygon2D] - - /// Create a new `GISGeographicMultiPolygon2D` - public init(polygons: [GeographicPolygon2D]) { - self.polygons = polygons - } -} - -extension GeographicMultiPolygon2D: GeometryConvertible, GeometryCollectable { - /// Convertible type - public typealias GeometryType = MultiPolygon - - public init(geometry polygon: GeometryType) { - let polygons = polygon.polygons.map { GeographicPolygon2D(geometry: $0) } - self.init(polygons: polygons) - } - - public var geometry: GeometryType { - let polygons = self.polygons.map { $0.geometry } - return .init(polygons: polygons, srid: FluentPostGISSrid) - } - - public var baseGeometry: Geometry { - return self.geometry - } -} - -extension GeographicMultiPolygon2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geographicMultiLineString } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeographicMultiPolygon2D, GeographicMultiPolygon2D) { - return (.init(polygons: []), - .init(polygons: [ GeographicPolygon2D(exteriorRing: GeographicLineString2D(points: [GeographicPoint2D(longitude: 0, latitude: 0)]))])) - } -} diff --git a/Sources/FluentPostGIS/Geography/DatabaseSchemaDataType+Geographic.swift b/Sources/FluentPostGIS/Geography/DatabaseSchemaDataType+Geographic.swift new file mode 100644 index 0000000..6ea9026 --- /dev/null +++ b/Sources/FluentPostGIS/Geography/DatabaseSchemaDataType+Geographic.swift @@ -0,0 +1,32 @@ +import FluentKit +import SQLKit + +extension DatabaseSchema.DataType { + public static var geographicPoint2D: DatabaseSchema.DataType { + .custom(SQLRaw("geography(Point, \(FluentPostGISSrid))")) + } + + public static var geographicLineString2D: DatabaseSchema.DataType { + .custom(SQLRaw("geography(LineString, \(FluentPostGISSrid))")) + } + + public static var geographicPolygon2D: DatabaseSchema.DataType { + .custom(SQLRaw("geography(Polygon, \(FluentPostGISSrid))")) + } + + public static var geographicMultiPoint2D: DatabaseSchema.DataType { + .custom(SQLRaw("geography(MultiPoint, \(FluentPostGISSrid))")) + } + + public static var geographicMultiLineString2D: DatabaseSchema.DataType { + .custom(SQLRaw("geography(MultiLineString, \(FluentPostGISSrid))")) + } + + public static var geographicMultiPolygon2D: DatabaseSchema.DataType { + .custom(SQLRaw("geography(MultiPolygon, \(FluentPostGISSrid))")) + } + + public static var geographicGeometryCollection2D: DatabaseSchema.DataType { + .custom(SQLRaw("geography(GeometryCollection, \(FluentPostGISSrid))")) + } +} diff --git a/Sources/FluentPostGIS/Geographic/GeographicGeometryCollection2D.swift b/Sources/FluentPostGIS/Geography/GeographicGeometryCollection2D.swift similarity index 68% rename from Sources/FluentPostGIS/Geographic/GeographicGeometryCollection2D.swift rename to Sources/FluentPostGIS/Geography/GeographicGeometryCollection2D.swift index d5241e4..eafc4e1 100644 --- a/Sources/FluentPostGIS/Geographic/GeographicGeometryCollection2D.swift +++ b/Sources/FluentPostGIS/Geography/GeographicGeometryCollection2D.swift @@ -1,9 +1,7 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeographicGeometryCollection2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { - +public struct GeographicGeometryCollection2D: Codable, Equatable, CustomStringConvertible { /// The points public let geometries: [GeometryCollectable] @@ -11,15 +9,14 @@ public struct GeographicGeometryCollection2D: Codable, Equatable, CustomStringCo public init(geometries: [GeometryCollectable]) { self.geometries = geometries } - } extension GeographicGeometryCollection2D: GeometryConvertible, GeometryCollectable { /// Convertible type public typealias GeometryType = GeometryCollection - + public init(geometry: GeometryCollection) { - geometries = geometry.geometries.map { + self.geometries = geometry.geometries.map { if let value = $0 as? Point { return GeographicPoint2D(geometry: value) } else if let value = $0 as? LineString { @@ -40,34 +37,37 @@ extension GeographicGeometryCollection2D: GeometryConvertible, GeometryCollectab } } } - + public var geometry: GeometryCollection { - let geometries = self.geometries.map { $0.baseGeometry } + let geometries = geometries.map(\.baseGeometry) return .init(geometries: geometries, srid: FluentPostGISSrid) } - + public var baseGeometry: Geometry { - return self.geometry + self.geometry } - + public init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer().decode(String.self) let wkbGeometry: GeometryCollection = try WKTDecoder().decode(from: value) self.init(geometry: wkbGeometry) } - + public func encode(to encoder: Encoder) throws { let wktEncoder = WKTEncoder() - let value = wktEncoder.encode(geometry) + let value = wktEncoder.encode(self.geometry) var container = encoder.singleValueContainer() try container.encode(value) } - - public static func == (lhs: GeographicGeometryCollection2D, rhs: GeographicGeometryCollection2D) -> Bool { + + public static func == ( + lhs: GeographicGeometryCollection2D, + rhs: GeographicGeometryCollection2D + ) -> Bool { guard lhs.geometries.count == rhs.geometries.count else { return false } - for i in 0.. (GeographicGeometryCollection2D, GeographicGeometryCollection2D) { - return (.init(geometries: []), - .init(geometries: [ GeographicPolygon2D(exteriorRing: GeographicLineString2D(points: [GeographicPoint2D(longitude:0, latitude:0)]))])) - } -} diff --git a/Sources/FluentPostGIS/Geography/GeographicLineString2D.swift b/Sources/FluentPostGIS/Geography/GeographicLineString2D.swift new file mode 100644 index 0000000..918c272 --- /dev/null +++ b/Sources/FluentPostGIS/Geography/GeographicLineString2D.swift @@ -0,0 +1,30 @@ +import FluentKit +import WKCodable + +public struct GeographicLineString2D: Codable, Equatable, CustomStringConvertible { + /// The points + public var points: [GeographicPoint2D] + + /// Create a new `GISGeographicLineString2D` + public init(points: [GeographicPoint2D]) { + self.points = points + } +} + +extension GeographicLineString2D: GeometryConvertible, GeometryCollectable { + /// Convertible type + public typealias GeometryType = LineString + + public init(geometry lineString: GeometryType) { + let points = lineString.points.map { GeographicPoint2D(geometry: $0) } + self.init(points: points) + } + + public var geometry: GeometryType { + .init(points: self.points.map(\.geometry), srid: FluentPostGISSrid) + } + + public var baseGeometry: Geometry { + self.geometry + } +} diff --git a/Sources/FluentPostGIS/Geographic/GeographicMultiLineString2D.swift b/Sources/FluentPostGIS/Geography/GeographicMultiLineString2D.swift similarity index 51% rename from Sources/FluentPostGIS/Geographic/GeographicMultiLineString2D.swift rename to Sources/FluentPostGIS/Geography/GeographicMultiLineString2D.swift index 4ddaf04..86052c8 100644 --- a/Sources/FluentPostGIS/Geographic/GeographicMultiLineString2D.swift +++ b/Sources/FluentPostGIS/Geography/GeographicMultiLineString2D.swift @@ -1,11 +1,10 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeographicMultiLineString2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { +public struct GeographicMultiLineString2D: Codable, Equatable, CustomStringConvertible { /// The points public let lineStrings: [GeographicLineString2D] - + /// Create a new `GISGeographicMultiLineString2D` public init(lineStrings: [GeographicLineString2D]) { self.lineStrings = lineStrings @@ -15,30 +14,18 @@ public struct GeographicMultiLineString2D: Codable, Equatable, CustomStringConve extension GeographicMultiLineString2D: GeometryConvertible, GeometryCollectable { /// Convertible type public typealias GeometryType = MultiLineString - + public init(geometry polygon: GeometryType) { let lineStrings = polygon.lineStrings.map { GeographicLineString2D(geometry: $0) } self.init(lineStrings: lineStrings) } - + public var geometry: GeometryType { - let lineStrings = self.lineStrings.map { $0.geometry } + let lineStrings = lineStrings.map(\.geometry) return .init(lineStrings: lineStrings, srid: FluentPostGISSrid) } - - public var baseGeometry: Geometry { - return self.geometry - } -} -extension GeographicMultiLineString2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geographicMultiLineString } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeographicMultiLineString2D, GeographicMultiLineString2D) { - return (.init(lineStrings: []), - .init(lineStrings: [ GeographicLineString2D(points: [GeographicPoint2D(longitude: 0, latitude: 0)]) ])) + public var baseGeometry: Geometry { + self.geometry } } diff --git a/Sources/FluentPostGIS/Geography/GeographicMultiPoint2D.swift b/Sources/FluentPostGIS/Geography/GeographicMultiPoint2D.swift new file mode 100644 index 0000000..af18549 --- /dev/null +++ b/Sources/FluentPostGIS/Geography/GeographicMultiPoint2D.swift @@ -0,0 +1,30 @@ +import FluentKit +import WKCodable + +public struct GeographicMultiPoint2D: Codable, Equatable, CustomStringConvertible { + /// The points + public var points: [GeographicPoint2D] + + /// Create a new `GISGeographicLineString2D` + public init(points: [GeographicPoint2D]) { + self.points = points + } +} + +extension GeographicMultiPoint2D: GeometryConvertible, GeometryCollectable { + /// Convertible type + public typealias GeometryType = MultiPoint + + public init(geometry lineString: GeometryType) { + let points = lineString.points.map { GeographicPoint2D(geometry: $0) } + self.init(points: points) + } + + public var geometry: GeometryType { + .init(points: self.points.map(\.geometry), srid: FluentPostGISSrid) + } + + public var baseGeometry: Geometry { + self.geometry + } +} diff --git a/Sources/FluentPostGIS/Geography/GeographicMultiPolygon2D.swift b/Sources/FluentPostGIS/Geography/GeographicMultiPolygon2D.swift new file mode 100644 index 0000000..08f8e07 --- /dev/null +++ b/Sources/FluentPostGIS/Geography/GeographicMultiPolygon2D.swift @@ -0,0 +1,31 @@ +import FluentKit +import WKCodable + +public struct GeographicMultiPolygon2D: Codable, Equatable, CustomStringConvertible { + /// The points + public let polygons: [GeographicPolygon2D] + + /// Create a new `GISGeographicMultiPolygon2D` + public init(polygons: [GeographicPolygon2D]) { + self.polygons = polygons + } +} + +extension GeographicMultiPolygon2D: GeometryConvertible, GeometryCollectable { + /// Convertible type + public typealias GeometryType = MultiPolygon + + public init(geometry polygon: GeometryType) { + let polygons = polygon.polygons.map { GeographicPolygon2D(geometry: $0) } + self.init(polygons: polygons) + } + + public var geometry: GeometryType { + let polygons = polygons.map(\.geometry) + return .init(polygons: polygons, srid: FluentPostGISSrid) + } + + public var baseGeometry: Geometry { + self.geometry + } +} diff --git a/Sources/FluentPostGIS/Geographic/GeographicPoint2D.swift b/Sources/FluentPostGIS/Geography/GeographicPoint2D.swift similarity index 52% rename from Sources/FluentPostGIS/Geographic/GeographicPoint2D.swift rename to Sources/FluentPostGIS/Geography/GeographicPoint2D.swift index ff648a7..9541066 100644 --- a/Sources/FluentPostGIS/Geographic/GeographicPoint2D.swift +++ b/Sources/FluentPostGIS/Geography/GeographicPoint2D.swift @@ -1,14 +1,13 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeographicPoint2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { +public struct GeographicPoint2D: Codable, Equatable, CustomStringConvertible { /// The point's x coordinate. public var longitude: Double - + /// The point's y coordinate. public var latitude: Double - + /// Create a new `GISGeographicPoint2D` public init(longitude: Double, latitude: Double) { self.longitude = longitude @@ -23,23 +22,12 @@ extension GeographicPoint2D: GeometryConvertible, GeometryCollectable { public init(geometry point: GeometryType) { self.init(longitude: point.x, latitude: point.y) } - + public var geometry: GeometryType { - return .init(vector: [self.longitude, self.latitude], srid: FluentPostGISSrid) + .init(vector: [self.longitude, self.latitude], srid: FluentPostGISSrid) } - - public var baseGeometry: Geometry { - return self.geometry - } -} -extension GeographicPoint2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geographicPoint } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeographicPoint2D, GeographicPoint2D) { - return (.init(longitude: 0, latitude: 0), .init(longitude: 1, latitude: 1)) + public var baseGeometry: Geometry { + self.geometry } } diff --git a/Sources/FluentPostGIS/Geographic/GeographicPolygon2D.swift b/Sources/FluentPostGIS/Geography/GeographicPolygon2D.swift similarity index 53% rename from Sources/FluentPostGIS/Geographic/GeographicPolygon2D.swift rename to Sources/FluentPostGIS/Geography/GeographicPolygon2D.swift index b2533ec..af36b77 100644 --- a/Sources/FluentPostGIS/Geographic/GeographicPolygon2D.swift +++ b/Sources/FluentPostGIS/Geography/GeographicPolygon2D.swift @@ -1,16 +1,15 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeographicPolygon2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { +public struct GeographicPolygon2D: Codable, Equatable, CustomStringConvertible { /// The points public let exteriorRing: GeographicLineString2D public let interiorRings: [GeographicLineString2D] - + public init(exteriorRing: GeographicLineString2D) { self.init(exteriorRing: exteriorRing, interiorRings: []) } - + /// Create a new `GISGeographicPolygon2D` public init(exteriorRing: GeographicLineString2D, interiorRings: [GeographicLineString2D]) { self.exteriorRing = exteriorRing @@ -21,32 +20,24 @@ public struct GeographicPolygon2D: Codable, Equatable, CustomStringConvertible, extension GeographicPolygon2D: GeometryConvertible, GeometryCollectable { /// Convertible type public typealias GeometryType = WKCodable.Polygon - + public init(geometry polygon: GeometryType) { let exteriorRing = GeographicLineString2D(geometry: polygon.exteriorRing) let interiorRings = polygon.interiorRings.map { GeographicLineString2D(geometry: $0) } self.init(exteriorRing: exteriorRing, interiorRings: interiorRings) } - + public var geometry: GeometryType { - let exteriorRing = self.exteriorRing.geometry - let interiorRings = self.interiorRings.map { $0.geometry } - return .init(exteriorRing: exteriorRing, interiorRings: interiorRings, srid: FluentPostGISSrid) + let exteriorRing = exteriorRing.geometry + let interiorRings = interiorRings.map(\.geometry) + return .init( + exteriorRing: exteriorRing, + interiorRings: interiorRings, + srid: FluentPostGISSrid + ) } - - public var baseGeometry: Geometry { - return self.geometry - } -} -extension GeographicPolygon2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geographicPolygon } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeographicPolygon2D, GeographicPolygon2D) { - return (.init(exteriorRing: GeographicLineString2D(points: []), interiorRings: []), - .init(exteriorRing: GeographicLineString2D(points: [GeographicPoint2D(longitude: 0, latitude: 0)]), interiorRings: [])) + public var baseGeometry: Geometry { + self.geometry } } diff --git a/Sources/FluentPostGIS/Geometry/DatabaseSchemaDataType+Geometric.swift b/Sources/FluentPostGIS/Geometry/DatabaseSchemaDataType+Geometric.swift new file mode 100644 index 0000000..06734a3 --- /dev/null +++ b/Sources/FluentPostGIS/Geometry/DatabaseSchemaDataType+Geometric.swift @@ -0,0 +1,32 @@ +import FluentKit +import SQLKit + +extension DatabaseSchema.DataType { + public static var geometricPoint2D: DatabaseSchema.DataType { + .custom(SQLRaw("geometry(Point, \(FluentPostGISSrid))")) + } + + public static var geometricLineString2D: DatabaseSchema.DataType { + .custom(SQLRaw("geometry(LineString, \(FluentPostGISSrid))")) + } + + public static var geometricPolygon2D: DatabaseSchema.DataType { + .custom(SQLRaw("geometry(Polygon, \(FluentPostGISSrid))")) + } + + public static var geometricMultiPoint2D: DatabaseSchema.DataType { + .custom(SQLRaw("geometry(MultiPoint, \(FluentPostGISSrid))")) + } + + public static var geometricMultiLineString2D: DatabaseSchema.DataType { + .custom(SQLRaw("geometry(MultiLineString, \(FluentPostGISSrid))")) + } + + public static var geometricMultiPolygon2D: DatabaseSchema.DataType { + .custom(SQLRaw("geometry(MultiPolygon, \(FluentPostGISSrid))")) + } + + public static var geometricGeometryCollection2D: DatabaseSchema.DataType { + .custom(SQLRaw("geometry(GeometryCollection, \(FluentPostGISSrid))")) + } +} diff --git a/Sources/FluentPostGIS/Geometry/GeometricGeometryCollection2D.swift b/Sources/FluentPostGIS/Geometry/GeometricGeometryCollection2D.swift index f6573bd..caece2e 100644 --- a/Sources/FluentPostGIS/Geometry/GeometricGeometryCollection2D.swift +++ b/Sources/FluentPostGIS/Geometry/GeometricGeometryCollection2D.swift @@ -1,24 +1,21 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeometricGeometryCollection2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { - +public struct GeometricGeometryCollection2D: Codable, Equatable, CustomStringConvertible { /// The points public let geometries: [GeometryCollectable] - + /// Create a new `GISGeometricGeometryCollection2D` public init(geometries: [GeometryCollectable]) { self.geometries = geometries - } + } } extension GeometricGeometryCollection2D: GeometryConvertible, GeometryCollectable { - - public typealias GeometryType = GeometryCollection - + public typealias GeometryType = GeometryCollection + public init(geometry: GeometryType) { - geometries = geometry.geometries.map { + self.geometries = geometry.geometries.map { if let value = $0 as? Point { return GeometricPoint2D(geometry: value) } else if let value = $0 as? LineString { @@ -39,34 +36,24 @@ extension GeometricGeometryCollection2D: GeometryConvertible, GeometryCollectabl } } } - + public var geometry: GeometryType { - let geometries = self.geometries.map { $0.baseGeometry } + let geometries = geometries.map(\.baseGeometry) return .init(geometries: geometries, srid: FluentPostGISSrid) } - + public var baseGeometry: Geometry { - return self.geometry - } - - public init(from decoder: Decoder) throws { - let value = try decoder.singleValueContainer().decode(String.self) - let geometry: GeometryCollection = try WKTDecoder().decode(from: value) - self.init(geometry: geometry) + self.geometry } - - public func encode(to encoder: Encoder) throws { - let wktEncoder = WKTEncoder() - let value = wktEncoder.encode(geometry) - var container = encoder.singleValueContainer() - try container.encode(value) - } - - public static func == (lhs: GeometricGeometryCollection2D, rhs: GeometricGeometryCollection2D) -> Bool { + + public static func == ( + lhs: GeometricGeometryCollection2D, + rhs: GeometricGeometryCollection2D + ) -> Bool { guard lhs.geometries.count == rhs.geometries.count else { return false } - for i in 0.. (GeometricGeometryCollection2D, GeometricGeometryCollection2D) { - return (.init(geometries: []), - .init(geometries: [ GeometricPolygon2D(exteriorRing: GeometricLineString2D(points: [GeometricPoint2D(x:0, y:0)]))])) - } -} diff --git a/Sources/FluentPostGIS/Geometry/GeometricLineString2D.swift b/Sources/FluentPostGIS/Geometry/GeometricLineString2D.swift index 9aa2d69..b9e71fc 100644 --- a/Sources/FluentPostGIS/Geometry/GeometricLineString2D.swift +++ b/Sources/FluentPostGIS/Geometry/GeometricLineString2D.swift @@ -1,43 +1,30 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeometricLineString2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { +public struct GeometricLineString2D: Codable, Equatable, CustomStringConvertible { /// The points public var points: [GeometricPoint2D] - + /// Create a new `GISGeometricLineString2D` public init(points: [GeometricPoint2D]) { self.points = points } - } extension GeometricLineString2D: GeometryConvertible, GeometryCollectable { /// Convertible type public typealias GeometryType = LineString - + public init(geometry lineString: GeometryType) { let points = lineString.points.map { GeometricPoint2D(geometry: $0) } self.init(points: points) } - + public var geometry: GeometryType { - return .init(points: self.points.map { $0.geometry }, srid: FluentPostGISSrid) + .init(points: self.points.map(\.geometry), srid: FluentPostGISSrid) } - - public var baseGeometry: Geometry { - return self.geometry - } -} -extension GeometricLineString2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geometricLineString } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeometricLineString2D, GeometricLineString2D) { - return (.init(points: [GeometricPoint2D(x: 0, y: 0)]), .init(points: [])) + public var baseGeometry: Geometry { + self.geometry } } diff --git a/Sources/FluentPostGIS/Geometry/GeometricMultiLineString2D.swift b/Sources/FluentPostGIS/Geometry/GeometricMultiLineString2D.swift index 221f852..ee0d8b0 100644 --- a/Sources/FluentPostGIS/Geometry/GeometricMultiLineString2D.swift +++ b/Sources/FluentPostGIS/Geometry/GeometricMultiLineString2D.swift @@ -1,45 +1,31 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeometricMultiLineString2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { +public struct GeometricMultiLineString2D: Codable, Equatable, CustomStringConvertible { /// The points public let lineStrings: [GeometricLineString2D] - + /// Create a new `GISGeometricMultiLineString2D` public init(lineStrings: [GeometricLineString2D]) { self.lineStrings = lineStrings } - } extension GeometricMultiLineString2D: GeometryConvertible, GeometryCollectable { /// Convertible type public typealias GeometryType = MultiLineString - + public init(geometry polygon: GeometryType) { let lineStrings = polygon.lineStrings.map { GeometricLineString2D(geometry: $0) } self.init(lineStrings: lineStrings) } - + public var geometry: GeometryType { - let lineStrings = self.lineStrings.map { $0.geometry } + let lineStrings = lineStrings.map(\.geometry) return .init(lineStrings: lineStrings, srid: FluentPostGISSrid) } - - public var baseGeometry: Geometry { - return self.geometry - } -} -extension GeometricMultiLineString2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geometricMultiLineString } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeometricMultiLineString2D, GeometricMultiLineString2D) { - return (.init(lineStrings: []), - .init(lineStrings: [ GeometricLineString2D(points: [GeometricPoint2D(x: 0, y: 0)]) ])) + public var baseGeometry: Geometry { + self.geometry } } diff --git a/Sources/FluentPostGIS/Geometry/GeometricMultiPoint2D.swift b/Sources/FluentPostGIS/Geometry/GeometricMultiPoint2D.swift index 412ecb4..9ee7ed0 100644 --- a/Sources/FluentPostGIS/Geometry/GeometricMultiPoint2D.swift +++ b/Sources/FluentPostGIS/Geometry/GeometricMultiPoint2D.swift @@ -1,43 +1,30 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeometricMultiPoint2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { +public struct GeometricMultiPoint2D: Codable, Equatable, CustomStringConvertible { /// The points public var points: [GeometricPoint2D] - + /// Create a new `GISGeometricLineString2D` public init(points: [GeometricPoint2D]) { self.points = points } - } extension GeometricMultiPoint2D: GeometryConvertible, GeometryCollectable { /// Convertible type public typealias GeometryType = MultiPoint - + public init(geometry lineString: GeometryType) { let points = lineString.points.map { GeometricPoint2D(geometry: $0) } self.init(points: points) } - + public var geometry: GeometryType { - return MultiPoint(points: self.points.map { $0.geometry }, srid: FluentPostGISSrid) + MultiPoint(points: self.points.map(\.geometry), srid: FluentPostGISSrid) } - - public var baseGeometry: Geometry { - return self.geometry - } -} -extension GeometricMultiPoint2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geometricMultiPoint } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeometricMultiPoint2D, GeometricMultiPoint2D) { - return (.init(points: []), .init(points: [GeometricPoint2D(x: 0, y: 0)])) + public var baseGeometry: Geometry { + self.geometry } } diff --git a/Sources/FluentPostGIS/Geometry/GeometricMultiPolygon2D.swift b/Sources/FluentPostGIS/Geometry/GeometricMultiPolygon2D.swift index dcc4821..7502985 100644 --- a/Sources/FluentPostGIS/Geometry/GeometricMultiPolygon2D.swift +++ b/Sources/FluentPostGIS/Geometry/GeometricMultiPolygon2D.swift @@ -1,11 +1,10 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeometricMultiPolygon2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { +public struct GeometricMultiPolygon2D: Codable, Equatable, CustomStringConvertible { /// The points public let polygons: [GeometricPolygon2D] - + /// Create a new `GISGeometricMultiPolygon2D` public init(polygons: [GeometricPolygon2D]) { self.polygons = polygons @@ -15,30 +14,18 @@ public struct GeometricMultiPolygon2D: Codable, Equatable, CustomStringConvertib extension GeometricMultiPolygon2D: GeometryConvertible, GeometryCollectable { /// Convertible type public typealias GeometryType = MultiPolygon - + public init(geometry polygon: GeometryType) { let polygons = polygon.polygons.map { GeometricPolygon2D(geometry: $0) } self.init(polygons: polygons) } - + public var geometry: GeometryType { - let polygons = self.polygons.map { $0.geometry } + let polygons = polygons.map(\.geometry) return .init(polygons: polygons, srid: FluentPostGISSrid) } - - public var baseGeometry: Geometry { - return self.geometry - } -} -extension GeometricMultiPolygon2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geometricMultiLineString } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeometricMultiPolygon2D, GeometricMultiPolygon2D) { - return (.init(polygons: []), - .init(polygons: [ GeometricPolygon2D(exteriorRing: GeometricLineString2D(points: [GeometricPoint2D(x: 0, y: 0)]))])) + public var baseGeometry: Geometry { + self.geometry } } diff --git a/Sources/FluentPostGIS/Geometry/GeometricPoint2D.swift b/Sources/FluentPostGIS/Geometry/GeometricPoint2D.swift index bded50b..73a0a27 100644 --- a/Sources/FluentPostGIS/Geometry/GeometricPoint2D.swift +++ b/Sources/FluentPostGIS/Geometry/GeometricPoint2D.swift @@ -1,14 +1,13 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeometricPoint2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { +public struct GeometricPoint2D: Codable, Equatable, CustomStringConvertible { /// The point's x coordinate. public var x: Double - + /// The point's y coordinate. public var y: Double - + /// Create a new `GISGeometricPoint2D` public init(x: Double, y: Double) { self.x = x @@ -23,23 +22,12 @@ extension GeometricPoint2D: GeometryConvertible, GeometryCollectable { public init(geometry point: GeometryType) { self.init(x: point.x, y: point.y) } - + public var geometry: GeometryType { - return .init(vector: [self.x, self.y], srid: FluentPostGISSrid) + .init(vector: [self.x, self.y], srid: FluentPostGISSrid) } - - public var baseGeometry: Geometry { - return self.geometry - } -} -extension GeometricPoint2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geometricPoint } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeometricPoint2D, GeometricPoint2D) { - return (.init(x: 0, y: 0), .init(x: 1, y: 1)) + public var baseGeometry: Geometry { + self.geometry } } diff --git a/Sources/FluentPostGIS/Geometry/GeometricPolygon2D.swift b/Sources/FluentPostGIS/Geometry/GeometricPolygon2D.swift index 7830545..597dd4d 100644 --- a/Sources/FluentPostGIS/Geometry/GeometricPolygon2D.swift +++ b/Sources/FluentPostGIS/Geometry/GeometricPolygon2D.swift @@ -1,12 +1,11 @@ -import Foundation -import PostgreSQL +import FluentKit import WKCodable -public struct GeometricPolygon2D: Codable, Equatable, CustomStringConvertible, PostgreSQLDataConvertible { +public struct GeometricPolygon2D: Codable, Equatable, CustomStringConvertible { /// The points public let exteriorRing: GeometricLineString2D public let interiorRings: [GeometricLineString2D] - + public init(exteriorRing: GeometricLineString2D) { self.init(exteriorRing: exteriorRing, interiorRings: []) } @@ -16,11 +15,9 @@ public struct GeometricPolygon2D: Codable, Equatable, CustomStringConvertible, P self.exteriorRing = exteriorRing self.interiorRings = interiorRings } - } extension GeometricPolygon2D: GeometryConvertible, GeometryCollectable { - /// Convertible type public typealias GeometryType = WKCodable.Polygon @@ -31,24 +28,16 @@ extension GeometricPolygon2D: GeometryConvertible, GeometryCollectable { } public var geometry: GeometryType { - let exteriorRing = self.exteriorRing.geometry - let interiorRings = self.interiorRings.map { $0.geometry } - return .init(exteriorRing: exteriorRing, interiorRings: interiorRings, srid: FluentPostGISSrid) - } - - public var baseGeometry: Geometry { - return self.geometry + let exteriorRing = exteriorRing.geometry + let interiorRings = interiorRings.map(\.geometry) + return .init( + exteriorRing: exteriorRing, + interiorRings: interiorRings, + srid: FluentPostGISSrid + ) } -} -extension GeometricPolygon2D: PostgreSQLDataTypeStaticRepresentable, ReflectionDecodable { - - /// See `PostgreSQLDataTypeStaticRepresentable`. - public static var postgreSQLDataType: PostgreSQLDataType { return .geometricPolygon } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (GeometricPolygon2D, GeometricPolygon2D) { - return (.init(exteriorRing: GeometricLineString2D(points: []), interiorRings: []), - .init(exteriorRing: GeometricLineString2D(points: [GeometricPoint2D(x: 0, y: 0)]), interiorRings: [])) + public var baseGeometry: Geometry { + self.geometry } } diff --git a/Sources/FluentPostGIS/Geometry/GeometryConvertible.swift b/Sources/FluentPostGIS/Geometry/GeometryConvertible.swift index 5255b6a..e1e1531 100644 --- a/Sources/FluentPostGIS/Geometry/GeometryConvertible.swift +++ b/Sources/FluentPostGIS/Geometry/GeometryConvertible.swift @@ -1,5 +1,6 @@ +import FluentKit +import Foundation import WKCodable -import PostgreSQL public protocol GeometryCollectable { var baseGeometry: Geometry { get } @@ -21,24 +22,23 @@ extension GeometryCollectable where Self: Equatable { extension GeometryConvertible where Self: CustomStringConvertible { public var description: String { - return WKTEncoder().encode(geometry) + WKTEncoder().encode(geometry) } } -extension GeometryConvertible where Self: Codable { - public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Self { - if let value = data.binary { - let decoder = WKBDecoder() - let geometry: GeometryType = try decoder.decode(from: value) - return self.init(geometry: geometry) - } else { - throw PostGISError.decode(self, from: data) - } +extension GeometryConvertible { + public init(from decoder: Decoder) throws { + let value = try decoder.singleValueContainer().decode(Data.self) + let decoder = WKBDecoder() + let geometry: GeometryType = try decoder.decode(from: value) + self.init(geometry: geometry) } - - public func convertToPostgreSQLData() throws -> PostgreSQLData { - let encoder = WKBEncoder(byteOrder: .littleEndian) - let data = encoder.encode(geometry) - return PostgreSQLData(.geometry, binary: data) + + public func encode(to encoder: Encoder) throws { + let wkEncoder = WKBEncoder(byteOrder: .littleEndian) + let data = wkEncoder.encode(geometry) + + var container = encoder.singleValueContainer() + try container.encode(data) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Contains.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Contains.swift index 99f6677..0785b01 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Contains.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Contains.swift @@ -1,12 +1,7 @@ -import FluentPostgreSQL +import FluentSQL +import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { /// Applies an ST_Contains filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -18,10 +13,13 @@ extension QueryBuilder where /// - value: Geometry value to filter by. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryContains(_ key: KeyPath, _ value: V) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryContains(_ field: KeyPath, _ value: V) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryContains(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(value)) + self.filterGeometryContains( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value) + ) } /// Applies an ST_Contains filter to this query. Usually you will use the filter operators to do this. @@ -35,71 +33,23 @@ extension QueryBuilder where /// - key: Swift `KeyPath` to a field on the model to filter. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryContains(_ value: V, _ key: KeyPath) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryContains(_ value: V, _ field: KeyPath) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryContains(Database.queryFilterValueGeometry(value), Database.queryField(.keyPath(key))) - } - - /// Applies an ST_Contains filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryContains("area", point) - /// .all() - /// - /// - parameters: - /// - field: Name to a field on the model to filter. - /// - value: Value to filter by. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryContains(_ field: Database.QueryField, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryGeometryContains(field, value)) - } - - /// Applies an ST_Contains filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryContains(point, "area") - /// .all() - /// - /// - parameters: - /// - value: Value to filter by. - /// - field: Name to a field on the model to filter. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryContains(_ value: Database.QueryFilterValue, _ field: Database.QueryField) -> Self { - return self.filter(custom: Database.queryGeometryContains(value, field)) + self.filterGeometryContains( + QueryBuilder.queryExpressionGeometry(value), + QueryBuilder.path(field) + ) } } -extension QuerySupporting where - QueryFilterValue: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ +extension QueryBuilder { /// Creates an instance of `QueryFilter` for ST_Contains from a field and value. /// /// - parameters: /// - field: Field to filter. /// - value: Value type. - public static func queryGeometryContains(_ field: QueryField, _ value: QueryFilterValue) -> QueryFilter { - let args = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Contains", args) - } - - /// Creates an instance of `QueryFilter` for ST_Contains from a field and value. - /// - /// - parameters: - /// - value: Value type. - /// - field: Field to filter. - public static func queryGeometryContains(_ value: QueryFilterValue, _ field: QueryField) -> QueryFilter { - let args = [ - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Contains", args) + public func filterGeometryContains(_ args: SQLExpression...) -> Self { + self.filter(function: "ST_Contains", args: args) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Crosses.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Crosses.swift index 072b19e..9a146c5 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Crosses.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Crosses.swift @@ -1,12 +1,7 @@ -import FluentPostgreSQL +import FluentSQL +import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { /// Applies an ST_Crosses filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -18,12 +13,15 @@ extension QueryBuilder where /// - value: Geometry value to filter by. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryCrosses(_ key: KeyPath, _ filter: V) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryCrosses(_ field: KeyPath, _ value: V) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryCrosses(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(filter)) + self.filterGeometryCrosses( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value) + ) } - + /// Applies an ST_Crosses filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -35,71 +33,23 @@ extension QueryBuilder where /// - key: Swift `KeyPath` to a field on the model to filter. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryCrosses(_ value: V, _ key: KeyPath) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryCrosses(_ value: V, _ field: KeyPath) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryCrosses(Database.queryFilterValueGeometry(value), Database.queryField(.keyPath(key))) + self.filterGeometryCrosses( + QueryBuilder.queryExpressionGeometry(value), + QueryBuilder.path(field) + ) } - - /// Applies an ST_Crosses filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryCrosses("area", path) - /// .all() - /// - /// - parameters: - /// - field: Name to a field on the model to filter. - /// - value: Value to filter by. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryCrosses(_ field: Database.QueryField, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryGeometryCrosses(field, value)) - } - - /// Applies an ST_Crosses filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryCrosses(area, "path") - /// .all() - /// - /// - parameters: - /// - value: Value to filter by. - /// - field: Name to a field on the model to filter. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryCrosses(_ value: Database.QueryFilterValue, _ field: Database.QueryField) -> Self { - return self.filter(custom: Database.queryGeometryCrosses(value, field)) - } } -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ +extension QueryBuilder { /// Creates an instance of `QueryFilter` for ST_Crosses from a field and value. /// /// - parameters: /// - field: Field to filter. /// - value: Value type. - public static func queryGeometryCrosses(_ field: QueryField, _ value: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Crosses", args) - } - - /// Creates an instance of `QueryFilter` for ST_Crosses from a field and value. - /// - /// - parameters: - /// - value: Value type. - /// - field: Field to filter. - public static func queryGeometryCrosses(_ value: QueryFilterValue, _ field: QueryField) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Crosses", args) + public func filterGeometryCrosses(_ args: SQLExpression...) -> Self { + self.filter(function: "ST_Crosses", args: args) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Disjoint.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Disjoint.swift index 30fb61a..8160482 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Disjoint.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Disjoint.swift @@ -1,12 +1,7 @@ -import FluentPostgreSQL +import FluentSQL +import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { /// Applies an ST_Disjoint filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -18,12 +13,15 @@ extension QueryBuilder where /// - value: Geometry value to filter by. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryDisjoint(_ key: KeyPath, _ filter: V) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryDisjoint(_ field: KeyPath, _ value: V) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryDisjoint(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(filter)) + self.filterGeometryDisjoint( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value) + ) } - + /// Applies an ST_Disjoint filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -35,71 +33,23 @@ extension QueryBuilder where /// - key: Swift `KeyPath` to a field on the model to filter. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryDisjoint(_ value: V, _ key: KeyPath) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryDisjoint(_ value: V, _ field: KeyPath) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryDisjoint(Database.queryFilterValueGeometry(value), Database.queryField(.keyPath(key))) - } - - /// Applies an ST_Disjoint filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryDisjoint("area", path) - /// .all() - /// - /// - parameters: - /// - field: Name to a field on the model to filter. - /// - value: Value to filter by. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryDisjoint(_ field: Database.QueryField, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryGeometryDisjoint(field, value)) - } - - /// Applies an ST_Disjoint filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryDisjoint(area, "path") - /// .all() - /// - /// - parameters: - /// - value: Value to filter by. - /// - field: Name to a field on the model to filter. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryDisjoint(_ value: Database.QueryFilterValue, _ field: Database.QueryField) -> Self { - return self.filter(custom: Database.queryGeometryDisjoint(value, field)) + self.filterGeometryDisjoint( + QueryBuilder.queryExpressionGeometry(value), + QueryBuilder.path(field) + ) } } -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ +extension QueryBuilder { /// Creates an instance of `QueryFilter` for ST_Disjoint from a field and value. /// /// - parameters: /// - field: Field to filter. /// - value: Value type. - public static func queryGeometryDisjoint(_ field: QueryField, _ value: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Disjoint", args) - } - - /// Creates an instance of `QueryFilter` for ST_Disjoint from a field and value. - /// - /// - parameters: - /// - value: Value type. - /// - field: Field to filter. - public static func queryGeometryDisjoint(_ value: QueryFilterValue, _ field: QueryField) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Disjoint", args) + public func filterGeometryDisjoint(_ args: SQLExpression...) -> Self { + self.filter(function: "ST_Disjoint", args: args) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Distance.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Distance.swift index 5121266..c4c6798 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Distance.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Distance.swift @@ -1,57 +1,32 @@ -import FluentPostgreSQL +import FluentSQL +import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { @discardableResult - public func filterGeometryDistance(_ key: KeyPath, _ filter: V, _ method: Database.QueryFilterMethod, _ value: Double) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryDistance( + _ field: KeyPath, + _ filter: V, + _ method: SQLBinaryOperator, + _ value: Double + ) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryDistance(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(filter), method, Database.queryFilterValue([value])) + self.filterGeometryDistance( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(filter), + method, + SQLLiteral.numeric(String(value)) + ) } - - @discardableResult - public func filterGeometryDistance(_ key: KeyPath, _ filter: Database.QueryFilterValue, _ method: Database.QueryFilterMethod, _ value: Double) -> Self - where T: GeometryConvertible - { - return filterGeometryDistance(Database.queryField(.keyPath(key)), filter, method, Database.queryFilterValue([value])) - } - - @discardableResult - public func filterGeometryDistance(_ key: KeyPath, _ filter: Database.QueryFilterValue, _ method: Database.QueryFilterMethod, _ value: Double) -> Self - where T: GeometryConvertible - { - return filterGeometryDistance(Database.queryField(.keyPath(key)), filter, method, Database.queryFilterValue([value])) - } - - @discardableResult - public func filterGeometryDistance(_ field: Database.QueryField, _ filter: Database.QueryFilterValue, _ method: Database.QueryFilterMethod, _ value: Double) -> Self - { - return filterGeometryDistance(field, filter, method, Database.queryFilterValue([value])) - } - - @discardableResult - private func filterGeometryDistance(_ field: Database.QueryField, _ filter: Database.QueryFilterValue, _ method: Database.QueryFilterMethod, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryFilterGeometryDistance(field, filter, method, value)) - } - } -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ - public static func queryFilterGeometryDistance(_ field: QueryField, _ filter: QueryFilterValue, _ method: QueryFilterMethod, _ value: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(filter as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .binary(.function("ST_Distance", args), method, value) +extension QueryBuilder { + public func filterGeometryDistance( + _ path: SQLExpression, + _ filter: SQLExpression, + _ method: SQLBinaryOperator, + _ value: SQLExpression + ) -> Self { + self.filter(.sql(SQLFunction("ST_Distance", args: [path, filter]), method, value)) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+DistanceWithin.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+DistanceWithin.swift index 6f12c5f..8f8e088 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+DistanceWithin.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+DistanceWithin.swift @@ -1,115 +1,61 @@ -import FluentPostgreSQL +import FluentSQL import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ - @discardableResult - public func filterGeometryDistanceWithin(_ key: KeyPath, _ filter: V, _ value: Double) -> Self - where T: GeometryConvertible, V: GeometryConvertible - { - return filterGeometryDistanceWithin(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(filter), Database.queryFilterValue([value])) - } - - @discardableResult - public func filterGeometryDistanceWithin(_ key: KeyPath, _ filter: Database.QueryFilterValue, _ value: Double) -> Self - where T: GeometryConvertible - { - return filterGeometryDistanceWithin(Database.queryField(.keyPath(key)), filter, Database.queryFilterValue([value])) - } - - @discardableResult - public func filterGeometryDistanceWithin(_ key: KeyPath, _ filter: Database.QueryFilterValue, _ value: Double) -> Self - where T: GeometryConvertible +extension QueryBuilder { + @discardableResult + public func filterGeometryDistanceWithin( + _ field: KeyPath, + _ value: Field.Value, + _ distance: Double + ) -> Self where Field: QueryableProperty, Field.Model == Model, + Field.Value: GeometryConvertible { - return filterGeometryDistanceWithin(Database.queryField(.keyPath(key)), filter, Database.queryFilterValue([value])) + self.filterDistanceWithin( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value), + SQLLiteral.numeric(String(distance)) + ) } - - @discardableResult - public func filterGeometryDistanceWithin(_ field: Database.QueryField, _ filter: Database.QueryFilterValue, _ value: Double) -> Self - { - return filterGeometryDistanceWithin(field, filter, Database.queryFilterValue([value])) - } - - @discardableResult - private func filterGeometryDistanceWithin(_ field: Database.QueryField, _ filter: Database.QueryFilterValue, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryFilterGeometryDistanceWithin(field, filter, value)) - } - + @discardableResult - public func filterGeographyDistanceWithin(_ key: KeyPath, _ filter: V, _ value: Double) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeographyDistanceWithin( + _ field: KeyPath, + _ value: Field.Value, + _ distance: Double + ) -> Self where Field: QueryableProperty, Field.Model == Model, + Field.Value: GeometryConvertible { - return filterGeographyDistanceWithin(Database.queryField(.keyPath(key)), Database.queryFilterValueGeographic(filter), Database.queryFilterValue([value])) + self.filterDistanceWithin( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeography(value), + SQLLiteral.numeric(String(distance)) + ) } - + @discardableResult - public func filterGeographyDistanceWithin(_ key: KeyPath, _ filter: V, _ valueKey: KeyPath) -> Self - where T: GeometryConvertible, V: GeometryConvertible, OtherResult: Model, OtherResult.Database == Database + public func filterGeographyDistanceWithin( + _ field: KeyPath, + _ value: Field.Value, + _ distance: KeyPath + ) -> Self + where Field: QueryableProperty, + Field.Model == Model, + Field.Value: GeometryConvertible, + OtherModel: Schema, + OtherField: QueryableProperty, + OtherField.Model == OtherModel, + OtherField.Value == Double { - return filterGeographyDistanceWithin(Database.queryField(.keyPath(key)), Database.queryFilterValueGeographic(filter), Database.queryField(.keyPath(valueKey))) - } - - @discardableResult - private func filterGeographyDistanceWithin(_ field: Database.QueryField, _ filter: Database.QueryFilterValue, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.filterGeographyDistanceWithin(field, filter, value)) - } - - @discardableResult - private func filterGeographyDistanceWithin(_ field: Database.QueryField, _ filter: Database.QueryFilterValue, _ value: Database.QueryField) -> Self { - return self.filter(custom: Database.filterGeographyDistanceWithin(field, filter, value)) - } - -} - -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ - public static func queryFilterGeometryDistanceWithin(_ field: QueryField, _ filter: QueryFilterValue, _ value: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(filter as! PostgreSQLExpression), - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_DWithin", args) - } -} - -extension QuerySupporting where QueryFilterValue: SQLExpression { - public static func queryFilterValueGeographic(_ geometry: T) -> QueryFilterValue { - let geometryText = WKTEncoder().encode(geometry.geometry) - return .function("ST_GeogFromText", [.expression(.literal(.string(geometryText)))]) + self.filterDistanceWithin( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeography(value), + SQLColumn(QueryBuilder.path(distance)) + ) } } -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ - public static func filterGeographyDistanceWithin(_ field: QueryField, _ filter: QueryFilterValue, _ value: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(filter as! PostgreSQLExpression), - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_DWithin", args) - } - - public static func filterGeographyDistanceWithin(_ field: QueryField, _ filter: QueryFilterValue, _ valueField: QueryField) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(filter as! PostgreSQLExpression), - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(valueField as! PostgreSQLColumnIdentifier)), - ] as! [QueryFilter.Function.Argument] - return .function("ST_DWithin", args) +extension QueryBuilder { + func filterDistanceWithin(_ args: SQLExpression...) -> Self { + self.filter(function: "ST_DWithin", args: args) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Equals.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Equals.swift index a6e726e..f599b46 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Equals.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Equals.swift @@ -1,12 +1,7 @@ -import FluentPostgreSQL +import FluentSQL +import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { /// Applies an ST_Equals filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -18,45 +13,23 @@ extension QueryBuilder where /// - value: Geometry value to filter by. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryEquals(_ key: KeyPath, _ filter: V) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryEquals(_ field: KeyPath, _ value: V) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryEquals(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(filter)) + self.filterGeometryEquals( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value) + ) } - - /// Applies an ST_Equals filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryDisjoint("area", path) - /// .all() - /// - /// - parameters: - /// - field: Name to a field on the model to filter. - /// - value: Value to filter by. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryEquals(_ field: Database.QueryField, _ filter: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryGeometryEquals(field, filter)) - } - } -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ +extension QueryBuilder { /// Creates an instance of `QueryFilter` for ST_Equals from a field and value. /// /// - parameters: /// - field: Field to filter. /// - value: Value type. - public static func queryGeometryEquals(_ field: QueryField, _ filter: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(filter as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Equals", args) + public func filterGeometryEquals(_ args: SQLExpression...) -> Self { + self.filter(function: "ST_Equals", args: args) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Helpers.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Helpers.swift new file mode 100644 index 0000000..8af9512 --- /dev/null +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Helpers.swift @@ -0,0 +1,31 @@ +import FluentSQL +import WKCodable + +extension QueryBuilder { + static func queryExpressionGeometry(_ geometry: T) -> SQLExpression { + let geometryText = WKTEncoder().encode(geometry.geometry) + return SQLFunction("ST_GeomFromEWKT", args: [SQLLiteral.string(geometryText)]) + } + + static func queryExpressionGeography(_ geometry: T) -> SQLExpression { + let geometryText = WKTEncoder().encode(geometry.geometry) + return SQLFunction("ST_GeogFromText", args: [SQLLiteral.string(geometryText)]) + } + + static func path(_ field: KeyPath) -> SQLExpression + where M: Schema, F: QueryableProperty, F.Model == M + { + let path = M.path(for: field).map(\.description).joined(separator: "_") + return SQLColumn(path) + } +} + +extension QueryBuilder { + func filter(function: String, args: [SQLExpression]) -> Self { + self.filter(.sql(SQLFunction(function, args: args))) + } + + func sort(function: String, args: [SQLExpression]) -> Self { + self.sort(.sql(SQLFunction(function, args: args))) + } +} diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Intersects.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Intersects.swift index e752401..93ef90d 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Intersects.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Intersects.swift @@ -1,12 +1,7 @@ -import FluentPostgreSQL +import FluentSQL +import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { /// Applies an ST_Intersects filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -18,12 +13,15 @@ extension QueryBuilder where /// - value: Geometry value to filter by. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryIntersects(_ key: KeyPath, _ filter: V) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryIntersects(_ field: KeyPath, _ value: V) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryIntersects(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(filter)) + self.filterGeometryIntersects( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value) + ) } - + /// Applies an ST_Intersects filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -35,71 +33,23 @@ extension QueryBuilder where /// - key: Swift `KeyPath` to a field on the model to filter. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryIntersects(_ value: V, _ key: KeyPath) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryIntersects(_ value: V, _ field: KeyPath) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryIntersects(Database.queryFilterValueGeometry(value), Database.queryField(.keyPath(key))) - } - - /// Applies an ST_Intersects filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryIntersects("area", path) - /// .all() - /// - /// - parameters: - /// - field: Name to a field on the model to filter. - /// - value: Value to filter by. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryIntersects(_ field: Database.QueryField, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryGeometryIntersects(field, value)) - } - - /// Applies an ST_Intersects filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryIntersects(area, "path") - /// .all() - /// - /// - parameters: - /// - value: Value to filter by. - /// - field: Name to a field on the model to filter. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryIntersects(_ value: Database.QueryFilterValue, _ field: Database.QueryField) -> Self { - return self.filter(custom: Database.queryGeometryIntersects(value, field)) + self.filterGeometryIntersects( + QueryBuilder.queryExpressionGeometry(value), + QueryBuilder.path(field) + ) } } -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ +extension QueryBuilder { /// Creates an instance of `QueryFilter` for ST_Intersects from a field and value. /// /// - parameters: /// - field: Field to filter. /// - value: Value type. - public static func queryGeometryIntersects(_ field: QueryField, _ value: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Intersects", args) - } - - /// Creates an instance of `QueryFilter` for ST_Intersects from a field and value. - /// - /// - parameters: - /// - value: Value type. - /// - field: Field to filter. - public static func queryGeometryIntersects(_ value: QueryFilterValue, _ field: QueryField) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Intersects", args) + public func filterGeometryIntersects(_ args: SQLExpression...) -> Self { + self.filter(function: "ST_Intersects", args: args) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Overlaps.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Overlaps.swift index 143892c..eb4887b 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Overlaps.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Overlaps.swift @@ -1,12 +1,7 @@ -import FluentPostgreSQL +import FluentSQL +import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { /// Applies an ST_Overlaps filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -18,12 +13,15 @@ extension QueryBuilder where /// - value: Geometry value to filter by. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryOverlaps(_ key: KeyPath, _ filter: V) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryOverlaps(_ field: KeyPath, _ value: V) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryOverlaps(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(filter)) + self.filterGeometryOverlaps( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value) + ) } - + /// Applies an ST_Overlaps filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -35,71 +33,23 @@ extension QueryBuilder where /// - key: Swift `KeyPath` to a field on the model to filter. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryOverlaps(_ value: V, _ key: KeyPath) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryOverlaps(_ value: V, _ field: KeyPath) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryOverlaps(Database.queryFilterValueGeometry(value), Database.queryField(.keyPath(key))) - } - - /// Applies an ST_Overlaps filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryOverlaps("area", path) - /// .all() - /// - /// - parameters: - /// - field: Name to a field on the model to filter. - /// - value: Value to filter by. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryOverlaps(_ field: Database.QueryField, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryGeometryOverlaps(field, value)) - } - - /// Applies an ST_Overlaps filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryOverlaps(area, "path") - /// .all() - /// - /// - parameters: - /// - value: Value to filter by. - /// - field: Name to a field on the model to filter. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryOverlaps(_ value: Database.QueryFilterValue, _ field: Database.QueryField) -> Self { - return self.filter(custom: Database.queryGeometryOverlaps(value, field)) + self.filterGeometryOverlaps( + QueryBuilder.queryExpressionGeometry(value), + QueryBuilder.path(field) + ) } } -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ +extension QueryBuilder { /// Creates an instance of `QueryFilter` for ST_Overlaps from a field and value. /// /// - parameters: /// - field: Field to filter. /// - value: Value type. - public static func queryGeometryOverlaps(_ field: QueryField, _ value: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Overlaps", args) - } - - /// Creates an instance of `QueryFilter` for ST_Overlaps from a field and value. - /// - /// - parameters: - /// - value: Value type. - /// - field: Field to filter. - public static func queryGeometryOverlaps(_ value: QueryFilterValue, _ field: QueryField) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Overlaps", args) + public func filterGeometryOverlaps(_ args: SQLExpression...) -> Self { + self.filter(function: "ST_Overlaps", args: args) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Sort.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Sort.swift index c1ef5f6..a9a3789 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Sort.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Sort.swift @@ -1,29 +1,30 @@ -import FluentPostgreSQL +import FluentKit +import FluentSQL -extension QueryBuilder where - Database: QuerySupporting, - Database.QuerySort: SQLOrderBy, - Database.QueryFilter: SQLExpression, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { + /// Applies an `ST_Distance` sort to this query. Usually you will use the filter operators to do this. + /// + /// let users = try User.query(on: conn) + /// .sortByDistance(\.$position, targetPosition) + /// .all() + /// + /// - parameters: + /// - key: Swift `KeyPath` to a field on the model to filter. + /// - value: Geometry value to filter by. + /// - returns: Query builder for chaining. @discardableResult - public func sortByDistance(between key: KeyPath, _ filter: V) -> Self - where T: GeometryConvertible, V: GeometryConvertible { - return self.sort(Database.distanceSort(between: Database.queryField(.keyPath(key)), Database.queryFilterValueGeographic(filter))) + public func sortByDistance(between field: KeyPath, _ value: V) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible + { + self.sortByDistance( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value) + ) } - } -extension QuerySupporting where - QuerySort: SQLOrderBy -{ - public static func distanceSort(between field: QueryField, _ filter: QueryFilterValue) -> QuerySort { - let args: [QuerySort.Expression.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(filter as! PostgreSQLExpression), - ] as! [QuerySort.Expression.Function.Argument] - return .orderBy(.function("ST_Distance", args), .ascending) +extension QueryBuilder { + public func sortByDistance(_ args: SQLExpression...) -> Self { + self.sort(function: "ST_Distance", args: args) } } - - diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Touches.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Touches.swift index db59c51..c6b305e 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Touches.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Touches.swift @@ -1,12 +1,7 @@ -import FluentPostgreSQL +import FluentSQL +import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { /// Applies an ST_Touches filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -18,12 +13,15 @@ extension QueryBuilder where /// - value: Geometry value to filter by. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryTouches(_ key: KeyPath, _ filter: K) -> Self - where T: GeometryConvertible, K: GeometryConvertible + public func filterGeometryTouches(_ field: KeyPath, _ value: V) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryTouches(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(filter)) + self.filterGeometryTouches( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value) + ) } - + /// Applies an ST_Touches filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -35,71 +33,23 @@ extension QueryBuilder where /// - key: Swift `KeyPath` to a field on the model to filter. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryTouches(_ value: V, _ key: KeyPath) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryTouches(_ value: V, _ field: KeyPath) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryTouches(Database.queryFilterValueGeometry(value), Database.queryField(.keyPath(key))) - } - - /// Applies an ST_Touches filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryTouches("area", path) - /// .all() - /// - /// - parameters: - /// - field: Name to a field on the model to filter. - /// - value: Value to filter by. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryTouches(_ field: Database.QueryField, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryGeometryTouches(field, value)) - } - - /// Applies an ST_Touches filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryTouches(area, "path") - /// .all() - /// - /// - parameters: - /// - value: Value to filter by. - /// - field: Name to a field on the model to filter. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryTouches(_ value: Database.QueryFilterValue, _ field: Database.QueryField) -> Self { - return self.filter(custom: Database.queryGeometryTouches(value, field)) + self.filterGeometryTouches( + QueryBuilder.queryExpressionGeometry(value), + QueryBuilder.path(field) + ) } } -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ +extension QueryBuilder { /// Creates an instance of `QueryFilter` for ST_Touches from a field and value. /// /// - parameters: /// - field: Field to filter. /// - value: Value type. - public static func queryGeometryTouches(_ field: QueryField, _ value: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Touches", args) - } - - /// Creates an instance of `QueryFilter` for ST_Touches from a field and value. - /// - /// - parameters: - /// - value: Value type. - /// - field: Field to filter. - public static func queryGeometryTouches(_ value: QueryFilterValue, _ field: QueryField) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Touches", args) + public func filterGeometryTouches(_ args: SQLExpression...) -> Self { + self.filter(function: "ST_Touches", args: args) } } diff --git a/Sources/FluentPostGIS/Queries/QueryBuilder+Within.swift b/Sources/FluentPostGIS/Queries/QueryBuilder+Within.swift index d23c73e..bfe66f8 100644 --- a/Sources/FluentPostGIS/Queries/QueryBuilder+Within.swift +++ b/Sources/FluentPostGIS/Queries/QueryBuilder+Within.swift @@ -1,12 +1,7 @@ -import FluentPostgreSQL +import FluentSQL +import WKCodable -extension QueryBuilder where - Database: QuerySupporting, - Database.QueryFilter: SQLExpression, - Database.QueryField == Database.QueryFilter.ColumnIdentifier, - Database.QueryFilterMethod == Database.QueryFilter.BinaryOperator, - Database.QueryFilterValue == Database.QueryFilter -{ +extension QueryBuilder { /// Applies an ST_Within filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -18,12 +13,15 @@ extension QueryBuilder where /// - value: Geometry value to filter by. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryWithin(_ key: KeyPath, _ filter: V) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryWithin(_ field: KeyPath, _ value: V) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryWithin(Database.queryField(.keyPath(key)), Database.queryFilterValueGeometry(filter)) + self.filterGeometryWithin( + QueryBuilder.path(field), + QueryBuilder.queryExpressionGeometry(value) + ) } - + /// Applies an ST_Within filter to this query. Usually you will use the filter operators to do this. /// /// let users = try User.query(on: conn) @@ -35,71 +33,23 @@ extension QueryBuilder where /// - key: Swift `KeyPath` to a field on the model to filter. /// - returns: Query builder for chaining. @discardableResult - public func filterGeometryWithin(_ value: V, _ key: KeyPath) -> Self - where T: GeometryConvertible, V: GeometryConvertible + public func filterGeometryWithin(_ value: V, _ field: KeyPath) -> Self + where F: QueryableProperty, F.Model == Model, V: GeometryConvertible { - return filterGeometryWithin(Database.queryFilterValueGeometry(value), Database.queryField(.keyPath(key))) - } - - /// Applies an ST_Within filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryWithin("area", path) - /// .all() - /// - /// - parameters: - /// - field: Name to a field on the model to filter. - /// - value: Value to filter by. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryWithin(_ field: Database.QueryField, _ value: Database.QueryFilterValue) -> Self { - return self.filter(custom: Database.queryGeometryWithin(field, value)) - } - - /// Applies an ST_Within filter to this query. Usually you will use the filter operators to do this. - /// - /// let users = try User.query(on: conn) - /// .filterGeometryWithin(area, "path") - /// .all() - /// - /// - parameters: - /// - value: Value to filter by. - /// - field: Name to a field on the model to filter. - /// - returns: Query builder for chaining. - @discardableResult - private func filterGeometryWithin(_ value: Database.QueryFilterValue, _ field: Database.QueryField) -> Self { - return self.filter(custom: Database.queryGeometryWithin(value, field)) + self.filterGeometryWithin( + QueryBuilder.queryExpressionGeometry(value), + QueryBuilder.path(field) + ) } } -extension QuerySupporting where - QueryFilter: SQLExpression, - QueryField == QueryFilter.ColumnIdentifier, - QueryFilterMethod == QueryFilter.BinaryOperator, - QueryFilterValue == QueryFilter -{ +extension QueryBuilder { /// Creates an instance of `QueryFilter` for ST_Within from a field and value. /// /// - parameters: /// - field: Field to filter. /// - value: Value type. - public static func queryGeometryWithin(_ field: QueryField, _ value: QueryFilterValue) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Within", args) - } - - /// Creates an instance of `QueryFilter` for ST_Within from a field and value. - /// - /// - parameters: - /// - value: Value type. - /// - field: Field to filter. - public static func queryGeometryWithin(_ value: QueryFilterValue, _ field: QueryField) -> QueryFilter { - let args: [QueryFilter.Function.Argument] = [ - GenericSQLFunctionArgument.expression(value as! PostgreSQLExpression), GenericSQLFunctionArgument.expression(PostgreSQLExpression.column(field as! PostgreSQLColumnIdentifier)), - ] as! [QueryFilter.Function.Argument] - return .function("ST_Within", args) + public func filterGeometryWithin(_ args: SQLExpression...) -> Self { + self.filter(function: "ST_Within", args: args) } } diff --git a/Sources/FluentPostGIS/Queries/QuerySupporting+Geometry.swift b/Sources/FluentPostGIS/Queries/QuerySupporting+Geometry.swift deleted file mode 100644 index c9a7b2c..0000000 --- a/Sources/FluentPostGIS/Queries/QuerySupporting+Geometry.swift +++ /dev/null @@ -1,9 +0,0 @@ -import FluentPostgreSQL -import WKCodable - -extension QuerySupporting where QueryFilterValue: SQLExpression { - public static func queryFilterValueGeometry(_ geometry: T) -> QueryFilterValue { - let geometryText = WKTEncoder().encode(geometry.geometry) - return .function("ST_GeomFromEWKT", [.expression(.literal(.string(geometryText)))]) - } -} diff --git a/Sources/FluentPostGIS/Support/EnablePostGISMigration.swift b/Sources/FluentPostGIS/Support/EnablePostGISMigration.swift index a98b263..01a40cb 100644 --- a/Sources/FluentPostGIS/Support/EnablePostGISMigration.swift +++ b/Sources/FluentPostGIS/Support/EnablePostGISMigration.swift @@ -1,14 +1,26 @@ -import Foundation -import FluentPostgreSQL +import FluentKit +import SQLKit -public struct EnablePostGISMigration: Migration { - public typealias Database = PostgreSQLDatabase +public struct EnablePostGISMigration: AsyncMigration { + public init() {} - public static func prepare(on conn: PostgreSQLConnection) -> Future { - return conn.raw("CREATE EXTENSION IF NOT EXISTS \"postgis\"").run() + public enum EnablePostGISMigrationError: Error { + case notSqlDatabase } - public static func revert(on conn: PostgreSQLConnection) -> Future { - return conn.raw("DROP EXTENSION IF EXISTS \"postgis\"").run() + public func prepare(on database: Database) async throws { + guard let db = database as? SQLDatabase else { + throw EnablePostGISMigrationError.notSqlDatabase + } + try await db.raw("CREATE EXTENSION IF NOT EXISTS \"postgis\"").run() + } + + public func revert(on database: Database) async throws { + guard let db = database as? SQLDatabase else { + throw EnablePostGISMigrationError.notSqlDatabase + } + try await db.raw("DROP EXTENSION IF EXISTS \"postgis\"").run() } } + +public var FluentPostGISSrid: UInt = 4326 diff --git a/Sources/FluentPostGIS/Support/FluentPostGISProvider.swift b/Sources/FluentPostGIS/Support/FluentPostGISProvider.swift deleted file mode 100644 index 5c937dd..0000000 --- a/Sources/FluentPostGIS/Support/FluentPostGISProvider.swift +++ /dev/null @@ -1,59 +0,0 @@ -import FluentPostgreSQL - -// Adds Fluent PostGIS to your project. -public final class FluentPostGISProvider: Provider { - public init() {} - - public func register(_ services: inout Services) throws { - } - - public func willBoot(_ worker: Container) throws -> Future { - return worker.withPooledConnection(to: .psql) { conn in - return FluentPostGISProvider._setup(on: conn) - } - } - - public static func _setup(on conn: PostgreSQLConnection) -> Future { - struct PGType: Codable { - var typname: String - var oid: Int32 - } - struct PGVersion: Codable { - var version: String - } - return EnablePostGISMigration.prepare(on: conn).then { - return conn.raw("SELECT PostGIS_Version() as version") - .all(decoding: PGVersion.self) - .map { rows in - guard rows.count > 0 else { - fatalError("PostGIS is not available") - } - PostGISVersion = rows.first?.version - }.then { - return conn.raw("select oid, typname from pg_type where typname IN ('geometry', 'geography')") - .all(decoding: PGType.self) - .map { rows in - guard rows.count > 0 else { - fatalError("PostGIS is not available") - } - rows.forEach { - switch $0.typname { - case "geometry": - PostgreSQLDataFormat.geometry = PostgreSQLDataFormat($0.oid) - case "geography": - PostgreSQLDataFormat.geography = PostgreSQLDataFormat($0.oid) - default: break - } - } - } - } - } - } - - public func didBoot(_ worker: Container) throws -> Future { - return .done(on: worker) - } -} - -public var PostGISVersion: String? -public var FluentPostGISSrid: UInt = 4326 diff --git a/Sources/FluentPostGIS/Support/PostGISDataTypeCode.swift b/Sources/FluentPostGIS/Support/PostGISDataTypeCode.swift deleted file mode 100644 index 8163cd7..0000000 --- a/Sources/FluentPostGIS/Support/PostGISDataTypeCode.swift +++ /dev/null @@ -1,11 +0,0 @@ -import PostgreSQL - -extension PostgreSQLDataFormat { - - /// Dynamic - public internal(set) static var geometry = PostgreSQLDataFormat(0) - - /// Dynamic - public internal(set) static var geography = PostgreSQLDataFormat(0) - -} diff --git a/Sources/FluentPostGIS/Support/PostGISError.swift b/Sources/FluentPostGIS/Support/PostGISError.swift deleted file mode 100644 index 48ce526..0000000 --- a/Sources/FluentPostGIS/Support/PostGISError.swift +++ /dev/null @@ -1,71 +0,0 @@ -import Debugging -import PostgreSQL - -/// Errors that can be thrown while working with PostgreSQL. -public struct PostGISError: Debuggable { - /// See `Debuggable`. - public static let readableName = "PostGIS Error" - - /// Error communicating with PostgreSQL wire-protocol - static func decode(_ type: T.Type, from data: PostgreSQLData) -> PostGISError { - return .init(identifier: "decode", reason: "Could not decode \(T.self): \(data).") - } - - /// See `Debuggable`. - public let identifier: String - - /// See `Debuggable`. - public var reason: String - - /// See `Debuggable`. - public var sourceLocation: SourceLocation - - /// See `Debuggable`. - public var stackTrace: [String] - - /// See `Debuggable`. - public var possibleCauses: [String] - - /// See `Debuggable`. - public var suggestedFixes: [String] - - /// Create a new `PostGISError`. - public init( - identifier: String, - reason: String, - possibleCauses: [String] = [], - suggestedFixes: [String] = [], - file: String = #file, - function: String = #function, - line: UInt = #line, - column: UInt = #column - ) { - self.identifier = identifier - self.reason = reason - self.sourceLocation = SourceLocation(file: file, function: function, line: line, column: column, range: nil) - self.stackTrace = PostGISError.makeStackTrace() - self.possibleCauses = possibleCauses - self.suggestedFixes = suggestedFixes - } -} - -/// Only includes the supplied closure in non-release builds. -internal func debugOnly(_ body: () -> Void) { - assert({ body(); return true }()) -} - -/// Logs a runtime warning. -internal func WARNING(_ string: @autoclosure () -> String) { - print("[WARNING] [PostGIS] \(string())") -} - -/// Logs a runtime error. -internal func ERROR(_ string: @autoclosure () -> String) { - print("[Error] [PostGIS] \(string())") -} - -func VERBOSE(_ string: @autoclosure () -> (String)) { - #if VERBOSE - print("[VERBOSE] [PostGIS] \(string())") - #endif -} diff --git a/Sources/FluentPostGIS/Support/PostgreSQLDataType+PostGIS.swift b/Sources/FluentPostGIS/Support/PostgreSQLDataType+PostGIS.swift deleted file mode 100644 index f0d2216..0000000 --- a/Sources/FluentPostGIS/Support/PostgreSQLDataType+PostGIS.swift +++ /dev/null @@ -1,60 +0,0 @@ -import PostgreSQL - -extension PostgreSQLDataType { - - public static var geometricPoint: PostgreSQLDataType { - return .custom("geometry(Point, \(FluentPostGISSrid))") - } - - public static var geographicPoint: PostgreSQLDataType { - return .custom("geography(Point, \(FluentPostGISSrid))") - } - - public static var geometricLineString: PostgreSQLDataType { - return .custom("geometry(LineString, \(FluentPostGISSrid))") - } - - public static var geographicLineString: PostgreSQLDataType { - return .custom("geography(LineString, \(FluentPostGISSrid))") - } - - public static var geometricPolygon: PostgreSQLDataType { - return .custom("geometry(Polygon, \(FluentPostGISSrid))") - } - - public static var geographicPolygon: PostgreSQLDataType { - return .custom("geography(Polygon, \(FluentPostGISSrid))") - } - - public static var geometricMultiPoint: PostgreSQLDataType { - return .custom("geometry(MultiPoint, \(FluentPostGISSrid))") - } - - public static var geographicMultiPoint: PostgreSQLDataType { - return .custom("geography(MultiPoint, \(FluentPostGISSrid))") - } - - public static var geometricMultiLineString: PostgreSQLDataType { - return .custom("geometry(MultiLineString, \(FluentPostGISSrid))") - } - - public static var geographicMultiLineString: PostgreSQLDataType { - return .custom("geography(MultiLineString, \(FluentPostGISSrid))") - } - - public static var geometricMultiPolygon: PostgreSQLDataType { - return .custom("geometry(MultiPolygon, \(FluentPostGISSrid))") - } - - public static var geographicMultiPolygon: PostgreSQLDataType { - return .custom("geography(MultiPolygon, \(FluentPostGISSrid))") - } - - public static var geometricGeometryCollection: PostgreSQLDataType { - return .custom("geometry(GeometryCollection, \(FluentPostGISSrid))") - } - - public static var geographicGeometryCollection: PostgreSQLDataType { - return .custom("geography(GeometryCollection, \(FluentPostGISSrid))") - } -} diff --git a/Tests/FluentPostGISTests/FluentPostGISTestCase.swift b/Tests/FluentPostGISTests/FluentPostGISTestCase.swift new file mode 100644 index 0000000..5d4c64e --- /dev/null +++ b/Tests/FluentPostGISTests/FluentPostGISTestCase.swift @@ -0,0 +1,45 @@ +import FluentKit +import FluentPostgresDriver +import PostgresKit +import XCTest + +class FluentPostGISTestCase: XCTestCase { + var dbs: Databases! + var db: Database { + self.dbs.database( + logger: .init(label: "lib.fluent.postgis"), + on: self.dbs.eventLoopGroup.next() + )! + } + + override func setUp() async throws { + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let threadPool = NIOThreadPool(numberOfThreads: 1) + self.dbs = Databases(threadPool: threadPool, on: eventLoopGroup) + let configuration = PostgresConfiguration( + hostname: "localhost", + username: "fluentpostgis", + password: "fluentpostgis", + database: "postgis_tests" + ) + self.dbs.use(.postgres(configuration: configuration), as: .psql) + + for migration in self.migrations { + try await migration.prepare(on: self.db) + } + } + + override func tearDown() async throws { + for migration in self.migrations { + try await migration.revert(on: self.db) + } + } + + private let migrations: [AsyncMigration] = [ + UserLocationMigration(), + CityMigration(), + UserPathMigration(), + UserAreaMigration(), + UserCollectionMigration(), + ] +} diff --git a/Tests/FluentPostGISTests/GeometryTests.swift b/Tests/FluentPostGISTests/GeometryTests.swift index 52c27cc..5d30098 100644 --- a/Tests/FluentPostGISTests/GeometryTests.swift +++ b/Tests/FluentPostGISTests/GeometryTests.swift @@ -1,129 +1,68 @@ -import Async -import Core -import XCTest -import FluentBenchmark -import FluentPostgreSQL -import Fluent -import Foundation @testable import FluentPostGIS +import XCTest -final class GeometryTests: XCTestCase { - var benchmarker: Benchmarker! - var database: PostgreSQLDatabase! - - override func setUp() { - let config: PostgreSQLDatabaseConfig = .init( - hostname: "localhost", - port: 5432, - username: "postgres", - database: "postgis_tests" - ) - database = PostgreSQLDatabase(config: config) - let eventLoop = MultiThreadedEventLoopGroup(numberOfThreads: 1) - benchmarker = try! Benchmarker(database, on: eventLoop, onFail: XCTFail) - let conn = try! benchmarker.pool.requestConnection().wait() - defer { benchmarker.pool.releaseConnection(conn) } - try! FluentPostgreSQLProvider._setup(on: conn).wait() - try! FluentPostGISProvider._setup(on: conn).wait() - } - - func testPoint() throws { - struct UserLocation: PostgreSQLModel, Migration { - var id: Int? - var location: GeometricPoint2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserLocation.prepare(on: conn).wait() - defer { try! UserLocation.revert(on: conn).wait() } - +final class GeometryTests: FluentPostGISTestCase { + func testPoint() async throws { let point = GeometricPoint2D(x: 1, y: 2) - var user = UserLocation(id: nil, location: point) - user = try user.save(on: conn).wait() - - let fetched = try UserLocation.find(1, on: conn).wait() + let user = UserLocation(location: point) + try await user.save(on: self.db) + + let fetched = try await UserLocation.find(1, on: self.db) XCTAssertEqual(fetched?.location, point) - let all = try UserLocation.query(on: conn).filterGeometryDistanceWithin(\UserLocation.location, user.location, 1000).all().wait() + let all = try await UserLocation.query(on: self.db) + .filterGeometryDistanceWithin(\.$location, user.location, 1000) + .all() XCTAssertEqual(all.count, 1) } - - func testLineString() throws { - struct UserPath: PostgreSQLModel, Migration { - var id: Int? - var path: GeometricLineString2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserPath.prepare(on: conn).wait() - defer { try! UserPath.revert(on: conn).wait() } + func testLineString() async throws { let point = GeometricPoint2D(x: 1, y: 2) let point2 = GeometricPoint2D(x: 2, y: 3) let point3 = GeometricPoint2D(x: 3, y: 2) let lineString = GeometricLineString2D(points: [point, point2, point3, point]) - var user = UserPath(id: nil, path: lineString) - user = try user.save(on: conn).wait() + let user = UserPath(path: lineString) + try await user.save(on: self.db) - let fetched = try UserPath.find(1, on: conn).wait() + let fetched = try await UserPath.find(1, on: self.db) XCTAssertEqual(fetched?.path, lineString) } - func testPolygon() throws { - struct UserArea: PostgreSQLModel, Migration { - var id: Int? - var area: GeometricPolygon2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserArea.prepare(on: conn).wait() - defer { try! UserArea.revert(on: conn).wait() } - + func testPolygon() async throws { let point = GeometricPoint2D(x: 1, y: 2) let point2 = GeometricPoint2D(x: 2, y: 3) let point3 = GeometricPoint2D(x: 3, y: 2) let lineString = GeometricLineString2D(points: [point, point2, point3, point]) - let polygon = GeometricPolygon2D(exteriorRing: lineString, interiorRings: [lineString, lineString]) + let polygon = GeometricPolygon2D( + exteriorRing: lineString, + interiorRings: [lineString, lineString] + ) - var user = UserArea(id: nil, area: polygon) - user = try user.save(on: conn).wait() + let user = UserArea(area: polygon) + try await user.save(on: self.db) - let fetched = try UserArea.find(1, on: conn).wait() + let fetched = try await UserArea.find(1, on: self.db) XCTAssertEqual(fetched?.area, polygon) } - func testGeometryCollection() throws { - struct UserCollection: PostgreSQLModel, Migration { - var id: Int? - var collection: GeometricGeometryCollection2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserCollection.prepare(on: conn).wait() - defer { try! UserCollection.revert(on: conn).wait() } - + func testGeometryCollection() async throws { let point = GeometricPoint2D(x: 1, y: 2) let point2 = GeometricPoint2D(x: 2, y: 3) let point3 = GeometricPoint2D(x: 3, y: 2) let lineString = GeometricLineString2D(points: [point, point2, point3, point]) - let polygon = GeometricPolygon2D(exteriorRing: lineString, interiorRings: [lineString, lineString]) + let polygon = GeometricPolygon2D( + exteriorRing: lineString, + interiorRings: [lineString, lineString] + ) let geometries: [GeometryCollectable] = [point, point2, point3, lineString, polygon] let geometryCollection = GeometricGeometryCollection2D(geometries: geometries) - var user = UserCollection(id: nil, collection: geometryCollection) - user = try user.save(on: conn).wait() + let user = UserCollection(collection: geometryCollection) + try await user.save(on: self.db) - let fetched = try UserCollection.find(1, on: conn).wait() + let fetched = try await UserCollection.find(1, on: self.db) XCTAssertEqual(fetched?.collection, geometryCollection) } } diff --git a/Tests/FluentPostGISTests/QueryTests.swift b/Tests/FluentPostGISTests/QueryTests.swift index 82d8fbe..fb943c6 100644 --- a/Tests/FluentPostGISTests/QueryTests.swift +++ b/Tests/FluentPostGISTests/QueryTests.swift @@ -1,530 +1,421 @@ -import Async -import Core -import XCTest -import FluentBenchmark -import FluentPostgreSQL -import Fluent -import Foundation @testable import FluentPostGIS +import XCTest -final class QueryTests: XCTestCase { - var benchmarker: Benchmarker! - var database: PostgreSQLDatabase! - - override func setUp() { - let config: PostgreSQLDatabaseConfig = .init( - hostname: "localhost", - port: 5432, - username: "postgres", - database: "postgis_tests" - ) - database = PostgreSQLDatabase(config: config) - let eventLoop = MultiThreadedEventLoopGroup(numberOfThreads: 1) - benchmarker = try! Benchmarker(database, on: eventLoop, onFail: XCTFail) - let conn = try! benchmarker.pool.requestConnection().wait() - defer { benchmarker.pool.releaseConnection(conn) } - try! FluentPostgreSQLProvider._setup(on: conn).wait() - try! FluentPostGISProvider._setup(on: conn).wait() - } - - func testContains() throws { - struct UserArea: PostgreSQLModel, Migration { - var id: Int? - var area: GeometricPolygon2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserArea.prepare(on: conn).wait() - defer { try! UserArea.revert(on: conn).wait() } - +final class QueryTests: FluentPostGISTestCase { + func testContains() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) - - var user = UserArea(id: nil, area: polygon) - user = try user.save(on: conn).wait() - + + let user = UserArea(area: polygon) + try await user.save(on: self.db) + let testPoint = GeometricPoint2D(x: 5, y: 5) - let all = try UserArea.query(on: conn).filterGeometryContains(\UserArea.area, testPoint).all().wait() + let all = try await UserArea.query(on: self.db) + .filterGeometryContains(\.$area, testPoint) + .all() XCTAssertEqual(all.count, 1) } - - func testContainsReversed() throws { - struct UserLocation: PostgreSQLModel, Migration { - var id: Int? - var location: GeometricPoint2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserLocation.prepare(on: conn).wait() - defer { try! UserLocation.revert(on: conn).wait() } - + + func testContainsReversed() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) - + let testPoint = GeometricPoint2D(x: 5, y: 5) - var user = UserLocation(id: nil, location: testPoint) - user = try user.save(on: conn).wait() - - let all = try UserLocation.query(on: conn).filterGeometryContains(polygon, \UserLocation.location).all().wait() + let user = UserLocation(location: testPoint) + try await user.save(on: self.db) + + let all = try await UserLocation.query(on: self.db) + .filterGeometryContains(polygon, \.$location) + .all() XCTAssertEqual(all.count, 1) } - - func testContainsWithHole() throws { - struct UserArea: PostgreSQLModel, Migration { - var id: Int? - var area: GeometricPolygon2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserArea.prepare(on: conn).wait() - defer { try! UserArea.revert(on: conn).wait() } - + + func testContainsWithHole() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let hole = GeometricLineString2D(points: [ GeometricPoint2D(x: 2.5, y: 2.5), GeometricPoint2D(x: 7.5, y: 2.5), GeometricPoint2D(x: 7.5, y: 7.5), GeometricPoint2D(x: 2.5, y: 7.5), - GeometricPoint2D(x: 2.5, y: 2.5)]) + GeometricPoint2D(x: 2.5, y: 2.5), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing, interiorRings: [hole]) - - var user = UserArea(id: nil, area: polygon) - user = try user.save(on: conn).wait() - + + let user = UserArea(area: polygon) + try await user.save(on: self.db) + let testPoint = GeometricPoint2D(x: 5, y: 5) - let all = try UserArea.query(on: conn).filterGeometryContains(\UserArea.area, testPoint).all().wait() + let all = try await UserArea.query(on: self.db) + .filterGeometryContains(\.$area, testPoint) + .all() XCTAssertEqual(all.count, 0) - + let testPoint2 = GeometricPoint2D(x: 1, y: 5) - let all2 = try UserArea.query(on: conn).filterGeometryContains(\UserArea.area, testPoint2).all().wait() + let all2 = try await UserArea.query(on: self.db) + .filterGeometryContains(\.$area, testPoint2) + .all() XCTAssertEqual(all2.count, 1) } - - func testCrosses() throws { - struct UserArea: PostgreSQLModel, Migration { - var id: Int? - var area: GeometricPolygon2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserArea.prepare(on: conn).wait() - defer { try! UserArea.revert(on: conn).wait() } - + + func testCrosses() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) - + let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 15, y: 0), - GeometricPoint2D(x: 5, y: 5) - ]) - - var user = UserArea(id: nil, area: polygon) - user = try user.save(on: conn).wait() - - let all = try UserArea.query(on: conn).filterGeometryCrosses(\UserArea.area, testPath).all().wait() + GeometricPoint2D(x: 5, y: 5), + ]) + + let user = UserArea(area: polygon) + try await user.save(on: self.db) + + let all = try await UserArea.query(on: self.db) + .filterGeometryCrosses(\.$area, testPath) + .all() XCTAssertEqual(all.count, 1) } - - func testCrossesReversed() throws { - struct UserLocation: PostgreSQLModel, Migration { - var id: Int? - var path: GeometricLineString2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserLocation.prepare(on: conn).wait() - defer { try! UserLocation.revert(on: conn).wait() } + func testCrossesReversed() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) - + let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 15, y: 0), - GeometricPoint2D(x: 5, y: 5) - ]) + GeometricPoint2D(x: 5, y: 5), + ]) - var user = UserLocation(id: nil, path: testPath) - user = try user.save(on: conn).wait() - - let all = try UserLocation.query(on: conn).filterGeometryCrosses(polygon, \UserLocation.path).all().wait() + let user = UserPath(path: testPath) + try await user.save(on: self.db) + + let all = try await UserPath.query(on: self.db) + .filterGeometryCrosses(polygon, \.$path) + .all() XCTAssertEqual(all.count, 1) } - - func testDisjoint() throws { - struct UserArea: PostgreSQLModel, Migration { - var id: Int? - var area: GeometricPolygon2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserArea.prepare(on: conn).wait() - defer { try! UserArea.revert(on: conn).wait() } - + + func testDisjoint() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) - + let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 15, y: 0), - GeometricPoint2D(x: 11, y: 5) - ]) - - var user = UserArea(id: nil, area: polygon) - user = try user.save(on: conn).wait() - - let all = try UserArea.query(on: conn).filterGeometryDisjoint(\UserArea.area, testPath).all().wait() + GeometricPoint2D(x: 11, y: 5), + ]) + + let user = UserArea(area: polygon) + try await user.save(on: self.db) + + let all = try await UserArea.query(on: self.db) + .filterGeometryDisjoint(\.$area, testPath) + .all() XCTAssertEqual(all.count, 1) } - - func testDisjointReversed() throws { - struct UserLocation: PostgreSQLModel, Migration { - var id: Int? - var path: GeometricLineString2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserLocation.prepare(on: conn).wait() - defer { try! UserLocation.revert(on: conn).wait() } - + + func testDisjointReversed() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) - + let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 15, y: 0), - GeometricPoint2D(x: 11, y: 5) - ]) - - var user = UserLocation(id: nil, path: testPath) - user = try user.save(on: conn).wait() - - let all = try UserLocation.query(on: conn).filterGeometryDisjoint(polygon, \UserLocation.path).all().wait() + GeometricPoint2D(x: 11, y: 5), + ]) + + let user = UserPath(path: testPath) + try await user.save(on: self.db) + + let all = try await UserPath.query(on: self.db) + .filterGeometryDisjoint(polygon, \.$path) + .all() XCTAssertEqual(all.count, 1) } - - func testEquals() throws { - struct UserArea: PostgreSQLModel, Migration { - var id: Int? - var area: GeometricPolygon2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserArea.prepare(on: conn).wait() - defer { try! UserArea.revert(on: conn).wait() } - + + func testEquals() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) - - var user = UserArea(id: nil, area: polygon) - user = try user.save(on: conn).wait() - - let all = try UserArea.query(on: conn).filterGeometryEquals(\UserArea.area, polygon).all().wait() + + let user = UserArea(area: polygon) + try await user.save(on: self.db) + + let all = try await UserArea.query(on: self.db) + .filterGeometryEquals(\.$area, polygon) + .all() XCTAssertEqual(all.count, 1) } - - func testIntersects() throws { - struct UserArea: PostgreSQLModel, Migration { - var id: Int? - var area: GeometricPolygon2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserArea.prepare(on: conn).wait() - defer { try! UserArea.revert(on: conn).wait() } - + + func testIntersects() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) - + let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 15, y: 0), - GeometricPoint2D(x: 5, y: 5) - ]) - - var user = UserArea(id: nil, area: polygon) - user = try user.save(on: conn).wait() - - let all = try UserArea.query(on: conn).filterGeometryIntersects(\UserArea.area, testPath).all().wait() + GeometricPoint2D(x: 5, y: 5), + ]) + + let user = UserArea(area: polygon) + try await user.save(on: self.db) + + let all = try await UserArea.query(on: self.db) + .filterGeometryIntersects(\.$area, testPath) + .all() XCTAssertEqual(all.count, 1) } - - func testIntersectsReversed() throws { - struct UserLocation: PostgreSQLModel, Migration { - var id: Int? - var path: GeometricLineString2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserLocation.prepare(on: conn).wait() - defer { try! UserLocation.revert(on: conn).wait() } - + + func testIntersectsReversed() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) - + let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 15, y: 0), - GeometricPoint2D(x: 5, y: 5) - ]) - - var user = UserLocation(id: nil, path: testPath) - user = try user.save(on: conn).wait() - - let all = try UserLocation.query(on: conn).filterGeometryIntersects(polygon, \UserLocation.path).all().wait() + GeometricPoint2D(x: 5, y: 5), + ]) + + let user = UserPath(path: testPath) + try await user.save(on: self.db) + + let all = try await UserPath.query(on: self.db) + .filterGeometryIntersects(polygon, \.$path) + .all() XCTAssertEqual(all.count, 1) } - func testOverlaps() throws { - struct UserPath: PostgreSQLModel, Migration { - var id: Int? - var path: GeometricLineString2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserPath.prepare(on: conn).wait() - defer { try! UserPath.revert(on: conn).wait() } - + func testOverlaps() async throws { let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 15, y: 0), GeometricPoint2D(x: 5, y: 5), GeometricPoint2D(x: 6, y: 6), GeometricPoint2D(x: 0, y: 0), - ]) - + ]) + let testPath2 = GeometricLineString2D(points: [ GeometricPoint2D(x: 16, y: 0), GeometricPoint2D(x: 5, y: 5), GeometricPoint2D(x: 6, y: 6), GeometricPoint2D(x: 2, y: 0), - ]) + ]) - var user = UserPath(id: nil, path: testPath) - user = try user.save(on: conn).wait() - - let all = try UserPath.query(on: conn).filterGeometryOverlaps(\.path, testPath2).all().wait() + let user = UserPath(path: testPath) + try await user.save(on: self.db) + + let all = try await UserPath.query(on: self.db) + .filterGeometryOverlaps(\.$path, testPath2) + .all() XCTAssertEqual(all.count, 1) } - - func testOverlapsReversed() throws { - struct UserPath: PostgreSQLModel, Migration { - var id: Int? - var path: GeometricLineString2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserPath.prepare(on: conn).wait() - defer { try! UserPath.revert(on: conn).wait() } - + + func testOverlapsReversed() async throws { let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 15, y: 0), GeometricPoint2D(x: 5, y: 5), GeometricPoint2D(x: 6, y: 6), GeometricPoint2D(x: 0, y: 0), - ]) - + ]) + let testPath2 = GeometricLineString2D(points: [ GeometricPoint2D(x: 16, y: 0), GeometricPoint2D(x: 5, y: 5), GeometricPoint2D(x: 6, y: 6), GeometricPoint2D(x: 2, y: 0), - ]) - - var user = UserPath(id: nil, path: testPath) - user = try user.save(on: conn).wait() - - let all = try UserPath.query(on: conn).filterGeometryOverlaps(testPath2, \.path).all().wait() + ]) + + let user = UserPath(path: testPath) + try await user.save(on: self.db) + + let all = try await UserPath.query(on: self.db) + .filterGeometryOverlaps(testPath2, \.$path) + .all() XCTAssertEqual(all.count, 1) } - - func testTouches() throws { - struct UserPath: PostgreSQLModel, Migration { - var id: Int? - var path: GeometricLineString2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserPath.prepare(on: conn).wait() - defer { try! UserPath.revert(on: conn).wait() } - + + func testTouches() async throws { let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 1, y: 1), - GeometricPoint2D(x: 0, y: 2) - ]) - + GeometricPoint2D(x: 0, y: 2), + ]) + let testPoint = GeometricPoint2D(x: 0, y: 2) - - var user = UserPath(id: nil, path: testPath) - user = try user.save(on: conn).wait() - - let all = try UserPath.query(on: conn).filterGeometryTouches(\.path, testPoint).all().wait() + + let user = UserPath(path: testPath) + try await user.save(on: self.db) + + let all = try await UserPath.query(on: self.db) + .filterGeometryTouches(\.$path, testPoint) + .all() XCTAssertEqual(all.count, 1) } - - func testTouchesReversed() throws { - struct UserPath: PostgreSQLModel, Migration { - var id: Int? - var path: GeometricLineString2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserPath.prepare(on: conn).wait() - defer { try! UserPath.revert(on: conn).wait() } - + + func testTouchesReversed() async throws { let testPath = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 1, y: 1), - GeometricPoint2D(x: 0, y: 2) - ]) - + GeometricPoint2D(x: 0, y: 2), + ]) + let testPoint = GeometricPoint2D(x: 0, y: 2) - - var user = UserPath(id: nil, path: testPath) - user = try user.save(on: conn).wait() - - let all = try UserPath.query(on: conn).filterGeometryTouches(testPoint, \.path).all().wait() + + let user = UserPath(path: testPath) + try await user.save(on: self.db) + + let all = try await UserPath.query(on: self.db) + .filterGeometryTouches(testPoint, \.$path) + .all() XCTAssertEqual(all.count, 1) } - - func testWithin() throws { - struct UserArea: PostgreSQLModel, Migration { - var id: Int? - var area: GeometricPolygon2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserArea.prepare(on: conn).wait() - defer { try! UserArea.revert(on: conn).wait() } + func testWithin() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) let hole = GeometricLineString2D(points: [ GeometricPoint2D(x: 2.5, y: 2.5), GeometricPoint2D(x: 7.5, y: 2.5), GeometricPoint2D(x: 7.5, y: 7.5), GeometricPoint2D(x: 2.5, y: 7.5), - GeometricPoint2D(x: 2.5, y: 2.5)]) + GeometricPoint2D(x: 2.5, y: 2.5), + ]) let polygon2 = GeometricPolygon2D(exteriorRing: hole) - - var user = UserArea(id: nil, area: polygon2) - user = try user.save(on: conn).wait() - - let all = try UserArea.query(on: conn).filterGeometryWithin(\.area, polygon).all().wait() + + let user = UserArea(area: polygon2) + try await user.save(on: self.db) + + let all = try await UserArea.query(on: self.db) + .filterGeometryWithin(\.$area, polygon) + .all() XCTAssertEqual(all.count, 1) } - - func testWithinReversed() throws { - struct UserArea: PostgreSQLModel, Migration { - var id: Int? - var area: GeometricPolygon2D - } - let conn = try benchmarker.pool.requestConnection().wait() - conn.logger = DatabaseLogger(database: .psql, handler: PrintLogHandler()) - defer { benchmarker.pool.releaseConnection(conn) } - - try UserArea.prepare(on: conn).wait() - defer { try! UserArea.revert(on: conn).wait() } - + + func testWithinReversed() async throws { let exteriorRing = GeometricLineString2D(points: [ GeometricPoint2D(x: 0, y: 0), GeometricPoint2D(x: 10, y: 0), GeometricPoint2D(x: 10, y: 10), GeometricPoint2D(x: 0, y: 10), - GeometricPoint2D(x: 0, y: 0)]) + GeometricPoint2D(x: 0, y: 0), + ]) let polygon = GeometricPolygon2D(exteriorRing: exteriorRing) let hole = GeometricLineString2D(points: [ GeometricPoint2D(x: 2.5, y: 2.5), GeometricPoint2D(x: 7.5, y: 2.5), GeometricPoint2D(x: 7.5, y: 7.5), GeometricPoint2D(x: 2.5, y: 7.5), - GeometricPoint2D(x: 2.5, y: 2.5)]) + GeometricPoint2D(x: 2.5, y: 2.5), + ]) let polygon2 = GeometricPolygon2D(exteriorRing: hole) - - var user = UserArea(id: nil, area: polygon) - user = try user.save(on: conn).wait() - - let all = try UserArea.query(on: conn).filterGeometryWithin(polygon2, \.area).all().wait() + + let user = UserArea(area: polygon) + try await user.save(on: self.db) + + let all = try await UserArea.query(on: self.db) + .filterGeometryWithin(polygon2, \.$area) + .all() XCTAssertEqual(all.count, 1) } + + func testDistanceWithin() async throws { + let berlin = GeographicPoint2D(longitude: 13.41053, latitude: 52.52437) + + // 255 km from Berlin + let hamburg = City(location: GeographicPoint2D(longitude: 10.01534, latitude: 53.57532)) + try await hamburg.save(on: self.db) + + // 505 km from Berlin + let munich = City(location: GeographicPoint2D(longitude: 11.57549, latitude: 48.13743)) + try await munich.save(on: self.db) + + // 27 km from Berlin + let potsdam = City(location: GeographicPoint2D(longitude: 13.06566, latitude: 52.39886)) + try await potsdam.save(on: self.db) + + let all = try await City.query(on: self.db) + .filterGeographyDistanceWithin(\.$location, berlin, 30 * 1000) + .all() + + XCTAssertEqual(all.map(\.id), [potsdam].map(\.id)) + } + + func testSortByDistance() async throws { + let berlin = GeographicPoint2D(longitude: 13.41053, latitude: 52.52437) + + // 255 km from Berlin + let hamburg = City(location: GeographicPoint2D(longitude: 10.01534, latitude: 53.57532)) + try await hamburg.save(on: self.db) + + // 505 km from Berlin + let munich = City(location: GeographicPoint2D(longitude: 11.57549, latitude: 48.13743)) + try await munich.save(on: self.db) + + // 27 km from Berlin + let potsdam = City(location: GeographicPoint2D(longitude: 13.06566, latitude: 52.39886)) + try await potsdam.save(on: self.db) + + let all = try await City.query(on: self.db) + .sortByDistance(between: \.$location, berlin) + .all() + XCTAssertEqual(all.map(\.id), [potsdam, hamburg, munich].map(\.id)) + } } diff --git a/Tests/FluentPostGISTests/TestModels.swift b/Tests/FluentPostGISTests/TestModels.swift new file mode 100644 index 0000000..9194513 --- /dev/null +++ b/Tests/FluentPostGISTests/TestModels.swift @@ -0,0 +1,148 @@ +import FluentKit +import FluentPostGIS + +final class UserLocation: Model { + static let schema = "user_location" + + @ID(custom: .id, generatedBy: .database) + var id: Int? + + @Field(key: "location") + var location: GeometricPoint2D + + init() {} + + init(location: GeometricPoint2D) { + self.location = location + } +} + +struct UserLocationMigration: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema(UserLocation.schema) + .field(.id, .int, .identifier(auto: true)) + .field("location", .geometricPoint2D) + .create() + } + + func revert(on database: Database) async throws { + try await database.schema(UserLocation.schema).delete() + } +} + +/// A model for testing `GeographicPoint2D`-related functionality +final class City: Model { + static let schema = "city_location" + + @ID(custom: .id, generatedBy: .database) + var id: Int? + + @Field(key: "location") + var location: GeographicPoint2D + + init() {} + + init(location: GeographicPoint2D) { + self.location = location + } +} + +struct CityMigration: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema(City.schema) + .field(.id, .int, .identifier(auto: true)) + .field("location", .geographicPoint2D) + .create() + } + + func revert(on database: Database) async throws { + try await database.schema(City.schema).delete() + } +} + +final class UserPath: Model { + static var schema: String = "user_path" + + @ID(custom: .id, generatedBy: .database) + var id: Int? + + @Field(key: "path") + var path: GeometricLineString2D + + init() {} + + init(path: GeometricLineString2D) { + self.path = path + } +} + +struct UserPathMigration: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema(UserPath.schema) + .field(.id, .int, .identifier(auto: true)) + .field("path", .geometricLineString2D) + .create() + } + + func revert(on database: Database) async throws { + try await database.schema(UserPath.schema).delete() + } +} + +final class UserArea: Model { + static var schema: String = "user_area" + + @ID(custom: .id, generatedBy: .database) + var id: Int? + + @Field(key: "area") + var area: GeometricPolygon2D + + init() {} + + init(area: GeometricPolygon2D) { + self.area = area + } +} + +struct UserAreaMigration: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema(UserArea.schema) + .field(.id, .int, .identifier(auto: true)) + .field("area", .geometricPolygon2D) + .create() + } + + func revert(on database: Database) async throws { + try await database.schema(UserArea.schema).delete() + } +} + +final class UserCollection: Model { + static var schema: String = "user_collection" + + @ID(custom: .id, generatedBy: .database) + var id: Int? + + @Field(key: "collection") + var collection: GeometricGeometryCollection2D + + init() {} + + init(collection: GeometricGeometryCollection2D) { + self.collection = collection + } +} + +struct UserCollectionMigration: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema(UserCollection.schema) + .field(.id, .int, .identifier(auto: true)) + .field("collection", .geometricGeometryCollection2D) + .create() + } + + func revert(on database: Database) async throws { + try await database.schema(UserCollection.schema).delete() + } +} diff --git a/Tests/FluentPostGISTests/XCTestManifests.swift b/Tests/FluentPostGISTests/XCTestManifests.swift deleted file mode 100644 index f936511..0000000 --- a/Tests/FluentPostGISTests/XCTestManifests.swift +++ /dev/null @@ -1,40 +0,0 @@ -import XCTest - -extension GeometryTests { - static let __allTests = [ -// ("testGeometryCollection", testGeometryCollection), - ("testLineString", testLineString), - ("testPoint", testPoint), - ("testPolygon", testPolygon), - ] -} - -extension QueryTests { - static let __allTests = [ - ("testContains", testContains), - ("testContainsReversed", testContainsReversed), - ("testContainsWithHole", testContainsWithHole), - ("testCrosses", testCrosses), - ("testCrossesReversed", testCrossesReversed), - ("testDisjoint", testDisjoint), - ("testDisjointReversed", testDisjointReversed), - ("testEquals", testEquals), - ("testIntersects", testIntersects), - ("testIntersectsReversed", testIntersectsReversed), - ("testOverlaps", testOverlaps), - ("testOverlapsReversed", testOverlapsReversed), - ("testTouches", testTouches), - ("testTouchesReversed", testTouchesReversed), - ("testWithin", testWithin), - ("testWithinReversed", testWithinReversed), - ] -} - -#if !os(macOS) -public func __allTests() -> [XCTestCaseEntry] { - return [ - testCase(GeometryTests.__allTests), - testCase(QueryTests.__allTests), - ] -} -#endif diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 4e23e73..0000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,8 +0,0 @@ -import XCTest - -import FluentPostGISTests - -var tests = [XCTestCaseEntry]() -tests += FluentPostGISTests.__allTests() - -XCTMain(tests)