In Vapor 3 you could use filter
method with a SQLiteBinaryOperator, so you could create a query with a like operator. I'm trying to do the exact same thing in Vapor 4 but couldn't find anything for that.
Here's my code
Vapor 4
func queryUserMovies(_ req: Request) throws -> Future<[Users]> {
let title = req.parameters.get("title")!
return Movies.query(on: req.db).filter(\.$title == title).first().unwrap(or:Abort(.notFound, reason: "There's no movie")).flatMap{ movie in
return movie.$users.query(on: req.db).all()
}
}
Vapor 3
func queryUserMovies(_ req: Request) throws -> Future<[Users]> {
guard let movie = req.query[String.self, at: "movie"] else {
throw Abort(.badRequest, reason: "Not such movie")
}
return Movies.query(on: req).filter(\.title, .like, movie).first().unwrap(or:Abort(.notFound, reason: "There's no movie")).flatMap{ movie in
return movie.users.query(on: req).all()
}
}
Is there anything similar in Vapor 4 or do I need to perform a raw query in SQL?
The equivalent in Vapor 4 is:
func queryUserMovies(_ req: Request) throws -> Future<[Users]> {
let title = try req.query.get(String.self, at: "title")
return Movies.query(on: req.db)
.filter(\.$title, .custom("ilike"), title)
.first()
.unwrap(or:Abort(.notFound, reason: "There's no movie"))
.flatMap{ movie in
return movie.$users.query(on: req.db).all()
}
}
You can even perform a wider search to find anything containing that title:
.filter(\.$title, .custom("ilike"), "%\(title)%")
I just had the same issue, but with one more obstacle!
The setup is a Machine relation joined with a Category relation and I wanted to search for occurrences of one or more search-terms in the Machine.name and Category.name with one expression.
(Both the Machine and the Category relations have a name attribute.)
search is of the Type [String.SubSequence], so we can iterate over multiple search-term keywords, which must all be present somewhere in the names.
My solution was:
return Machine
.query(on: req.db)
.join(Category.self, on: \Category.$id == \Machine.$category.$id)
// For each search-term the must be at least one fit with Machine.name and/or Category.name
.group(.and) {
var result = $0
for term in search.map({ "%\(String($0))%" }) {
// One or both must fit the search-term ...
result = result.group(.or) {
$0
// Does the Machine name fit?
.filter(\Machine.$name, .custom("ilike"), term)
// Does the Category.path name fit?
.filter(
DatabaseQuery.Field.path(
Category.path(for: \Category.$name),
schema: Category.schema
),
DatabaseQuery.Filter.Method.custom("ilike"),
DatabaseQuery.Value.bind(term)
)
}
}
}
As you can see there are two .group(...) functions. The outer group (.or) says "for each search-term there must be one fitting inner group rule" and the inner group (.or) says "there must be at lease a fitting Machine.name or Category.name".
Since .custom("ilike") is not supported by the "joined-relation-filter" I used the workaround I found here.
Even tough the question may already be answered, I spend some time figuring this out and thought I could share it here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With