Skip to content

Commit 820459f

Browse files
committed
Fix ambiguous column references in spatial queries
Resolves an issue where column references were becoming ambiguous in complex joins involving geometry columns. This change modifies the path generation to explicitly qualify columns with their schema or table alias, replacing the previous approach that created unqualified column names. Specifically, we now prepend the schema or alias name with a dot separator before the column path, ensuring proper SQL column qualification. This prevents errors like "column reference is ambiguous" when using spatial functions on tables with similar column names in multi-table joins. Signed-off-by: bwdmr <[email protected]>
1 parent 14a5a68 commit 820459f

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

Sources/FluentPostGIS/Queries/QueryBuilder+Helpers.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ extension QueryBuilder {
1515
static func path<M, F>(_ field: KeyPath<M, F>) -> any SQLExpression
1616
where M: Schema, F: QueryableProperty, F.Model == M
1717
{
18-
let path = M.path(for: field).map(\.description).joined(separator: "_")
19-
return SQLColumn(path)
18+
let schema = SQLIdentifier(M.schemaOrAlias)
19+
let path = SQLIdentifier(M.path(for: field).map(\.description).joined(separator: "_"))
20+
return SQLColumn(path, table: schema)
2021
}
2122
}
2223

Tests/FluentPostGISTests/FluentPostGISTestCase.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import FluentKit
22
import FluentPostgresDriver
3+
import FluentPostGIS
34
import PostgresKit
45
import XCTest
56

@@ -11,7 +12,7 @@ class FluentPostGISTestCase: XCTestCase {
1112
on: self.dbs.eventLoopGroup.next()
1213
)!
1314
}
14-
15+
1516
override func setUp() async throws {
1617
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
1718
let threadPool = NIOThreadPool(numberOfThreads: 1)
@@ -25,6 +26,7 @@ class FluentPostGISTestCase: XCTestCase {
2526
)
2627
self.dbs.use(.postgres(configuration: configuration), as: .psql)
2728

29+
try await EnablePostGISMigration().prepare(on: self.db)
2830
for migration in self.migrations {
2931
try await migration.prepare(on: self.db)
3032
}
@@ -34,6 +36,7 @@ class FluentPostGISTestCase: XCTestCase {
3436
for migration in self.migrations {
3537
try await migration.revert(on: self.db)
3638
}
39+
try await EnablePostGISMigration().revert(on: self.db)
3740
}
3841

3942
private let migrations: [any AsyncMigration] = [
@@ -42,5 +45,6 @@ class FluentPostGISTestCase: XCTestCase {
4245
UserPathMigration(),
4346
UserAreaMigration(),
4447
UserCollectionMigration(),
48+
GuestMigration()
4549
]
4650
}

Tests/FluentPostGISTests/QueryTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
@testable import FluentPostGIS
2+
import FluentKit
23
import XCTest
34

45
final class QueryTests: FluentPostGISTestCase {
6+
func testAlias() {
7+
let query = Guest.query(on: self.db)
8+
.join(Host.self, on: \Guest.$host.$id == \Host.$id)
9+
10+
for join in query.query.joins {
11+
if case DatabaseQuery.Join.join(
12+
schema: let schema,
13+
alias: let alias?,
14+
let method,
15+
let foreign,
16+
let local
17+
) = join
18+
{ XCTAssertTrue(!alias.isEmpty) }
19+
}
20+
}
21+
522
func testContains() async throws {
623
let exteriorRing = GeometricLineString2D(points: [
724
GeometricPoint2D(x: 0, y: 0),

Tests/FluentPostGISTests/TestModels.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,42 @@
11
import FluentKit
22
import FluentPostGIS
33

4+
5+
6+
final class Host: ModelAlias {
7+
static let name = "host"
8+
let model = Guest()
9+
}
10+
11+
final class Guest: Model, @unchecked Sendable {
12+
static let schema: String = "guest"
13+
14+
@ID(custom: .id, generatedBy: .database)
15+
var id: Int?
16+
17+
@OptionalParent(key: "host_id")
18+
var host: Guest?
19+
20+
init() {}
21+
22+
init(hostID: Guest.IDValue? = nil) {
23+
self.$host.id = hostID
24+
}
25+
}
26+
27+
struct GuestMigration: AsyncMigration {
28+
func prepare(on database: any Database) async throws {
29+
try await database.schema(Guest.schema)
30+
.field(.id, .int, .identifier(auto: true))
31+
.field("host_id", .int, .references(Guest.schema, "id"))
32+
.create()
33+
}
34+
35+
func revert(on database: any Database) async throws {
36+
try await database.schema(Guest.schema).delete()
37+
}
38+
}
39+
440
final class UserLocation: Model, @unchecked Sendable {
541
static let schema = "user_location"
642

0 commit comments

Comments
 (0)