ORM são maravilhosos, até que eles vazem . Todos o fazem, eventualmente. Ecto é jovem (ou seja, só ganhou habilidade para
OR
where cláusulas juntas 30 dias atrás
), então simplesmente não é maduro o suficiente para desenvolver uma API que considere giros SQL avançados. Pesquisando opções possíveis, você não está sozinho na solicitação. A incapacidade de compreender listas em fragmentos (seja como parte de
order_by
ou where
ou em qualquer outro lugar) foi mencionado na Ecto issue #1485
, no StackOverflow
, no Fórum Elixir
e este postagem do blog
. O último é particularmente instrutivo. Mais sobre isso daqui a pouco. Primeiro, vamos tentar alguns experimentos. Experiência nº 1: Pode-se primeiro tentar usar
Kernel.apply/3
para passar a lista para fragment
, mas isso não vai funcionar:|> order_by(Kernel.apply(Ecto.Query.Builder, :fragment, ^ids))
Experiência nº 2: Então talvez possamos construí-lo com manipulação de strings. Que tal dar
fragment
uma string construída em tempo de execução com espaços reservados suficientes para que ela seja extraída da lista:|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(Enum.map(ids, fn _ -> "?" end), ","), ")"], ""), ^ids))
Qual produziria
FIELD(id,?,?,?)
dado ids = [1, 2, 3]
. Não, isso também não funciona. Experiência nº 3: Criando todo o SQL final construído a partir dos ids, colocando os valores brutos de ID diretamente na string composta. Além de ser horrível, também não funciona:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(^ids, ","), ")"], "")))
Experiência nº 4: Isso me leva a esse post do blog que mencionei. Nele, o autor fala sobre a falta de
or_where
usando um conjunto de macros predefinidas com base no número de condições para reunir:defp orderby_fragment(query, [v1]) do
from u in query, order_by: fragment("FIELD(id,?)", ^v1)
end
defp orderby_fragment(query, [v1,v2]) do
from u in query, order_by: fragment("FIELD(id,?,?)", ^v1, ^v2)
end
defp orderby_fragment(query, [v1,v2,v3]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3)
end
defp orderby_fragment(query, [v1,v2,v3,v4]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3, ^v4)
end
Embora isso funcione e use o ORM "com o grão", por assim dizer, ele requer que você tenha um número finito e gerenciável de campos disponíveis. Isso pode ou não ser um divisor de águas.
Minha recomendação:não tente fazer malabarismos com os vazamentos de um ORM. Você sabe a melhor consulta. Se o ORM não aceitar, escreva-o diretamente com SQL bruto e documente por que o ORM não funciona. Proteja-o atrás de uma função ou módulo para que você possa reservar o direito futuro de alterar sua implementação. Um dia, quando o ORM alcançar, você pode simplesmente reescrevê-lo bem, sem efeitos no resto do sistema.