Search code examples
swiftpostgresqlfluentvaporvapor-fluent

Unable to create a Fluent ORM query for querying nested parents


I have 3 tables, LastName, MiddleName and FirstName... FirstName has MiddleNameID as Parent, MiddleName has LastName.id as parent

Models look like this

final class LastName: Model, Content {
    static let schema = "lastnames"
    
    @ID(key: .id)
    var id: UUID?

    @Field(key: "name")
    var name: String

    
    @Children(for: \.$lastname)
    var middle_names: [MiddleName]
    
    init() { }

    init(id: UUID? = nil, name: String) {
        self.id = id
        self.name = name
    }
}

final class MiddleName: Model, Content {
    static let schema = "middlenames"
    
    @ID(key: .id)
    var id: UUID?

    @Parent(key: "last_name_id")
    var lastname: LastName
    
    @Field(key: "name")
    var name: String

    @Children(for: \.$middleNameId)
    var firstNames: [FirstName]
    
    init() { }

    init(id: UUID? = nil, lastname: LastName, name: String ) {
        self.id = id
        self.lastname = lastname
        self.name = name
    }
}


final class FirstName: Model, Content {
    static let schema = "firstnames"
    
    @ID(key: .id)
    var id: UUID?
    
    @Parent(key: "middle_name_id")
    var middleNameId: MiddleName
    
    
    @Field(key: "name")
    var name: String
   
    
    init() { }

    init(id: UUID? = nil, middleNameId: MiddleName, name: String) {
        self.id = id
        self.middleNameId = testBundleId
        self.name = name
    }
}

And i want the query such as give me all the first name records where lastname == Smith AND/OR middle == James... not sure how can I do a multiple join's using Fluent DSL? or maybe give me all first name which matches something from Middle Name and/or last name

if I had to write a SQL query probably will look like this

                  SELECT DISTINCT ON (ln.id, mn.id)
                  ln.name, mn.name, fn.*
                  FROM last_name ln
                  LEFT JOIN middle_name mn ON ln.id = mn.last_name_id
                  LEFT JOIN first_name fn ON fn.middle_name_id = mn.id
                  WHERE ( mn.name <> '' AND fn.name <> '' ) AND
                  (true AND ln.name ~ 'smith')
                  ORDER BY ln.id, mn.id, fn.id

Solution

  • You can do this in a Fluent query, something like:

    return Lastname.query(on: req)
        .filter(\.$name == "Smith")
        .with(\.$middle_names) { $0.with(\.$firstnames) }
        .all()
        .flatMap { wholeNames in
        let result = wholeNames.filter { $0.middle_names.contains("James") }
        // continue
    }
    

    This will give you all the Smiths who have a middle name of James. You can use it as the basis for the OR variant. It works by first filtering the surnames for Smith and then the with joins the middle name, but the closure ensures first names are joined as well.

    I don't think there is a way of building the middle-name filter into the query, so I think you have to do it as I have shown.