Tbi
Tbi

Reputation: 316

Swift Vapor Fluent: How do I query two joins

I am using Vapor 4 and I have the following MySQL Database structure: A City has reference to a POI and a POI has a reference to a Translation. How should I use the join statement. So that I get the Translation in the city model?

public final class City: Model {
    public static let schema = "cities"

    @ID(custom: "id")
    public var id: Int?
    @Parent(key: "poi_id")
    public var poi: POI
}

public final class POI: Model {
    public static let schema = "b_pois"

    @ID(custom: "id")
    public var id: Int?
    @OptionalParent(key: "name_translation_id")
    public var nameTranslation: Translation?
}

public final class Translation: Model {
    public static let schema = "b_translations"

    @ID(custom: "id")
    public var id: Int?

    @Field(key: "english")
    public var english: String

    @OptionalField(key: "german")
    public var german: String?
    ...
}

When I use the following query I expect the city model to have the value:

let cities = try await City.query(on: dataBase)
            .join(POI.self, on: \POI.$id == \City.$poi.$id)
            .join(Translation.self, on: \POI.$nameTranslation.$id == \Translation.$id)
            .with(\.$poi)
            .filter(City.self, \.$population >= population)
            .all()
// expected:
cities.first!.poi.nameTranslation != nil
// or
cities.first!.$poi.$nameTranslation.value != nil

But even though the database has a value and poi ist not nil I don't retrieve a value for the name translation. Am I missing something?

I need something like:

.with(\.$poi.$nameTranslation) 

But this is not possible.

Upvotes: 1

Views: 119

Answers (1)

Nick
Nick

Reputation: 5200

You can achieve what you want by nesting with. Something like:

 .with(\.$poi) { $0.with(\.$nameTranslation) }

Should do the job nicely!

You don't need the explicit JOINs if you are using Parent/OptionalParent, etc. Let fluent take care of it. You can then access the translated name (with suitable guard/if let, etc.):

city.poi.nameTranslation

Your query should boil down to:

let cities = try await City.query(on: dataBase)
            .with(\.$poi) { $0.with(\.$nameTranslation) }
            .filter(City.self, \.$population >= population)
            .all()

In vapor terms, this is called 'nested eager loading' and is documented here.

Upvotes: 2

Related Questions