A solution could be to create a function which pattern matches the params by matching in the %{"join" => "right_join"} and takes a query. This way random params can’t break the query.
import Ecto.Query
def do_join(query, %{"join" => "right_join"}) do
query
|> join(:right_join, [q], assoc(q, :customer)
end
def do_join(query, _), do: query
The function returns a query which you can then send to a Repo.all or something similar.