ORM is geweldig, totdat ze lekken . Ze doen het allemaal, uiteindelijk. Ecto is jong (bijv. het kreeg alleen de mogelijkheid om OR
waar clausules samen 30 dagen geleden
), dus het is gewoon niet volwassen genoeg om een API te hebben ontwikkeld die rekening houdt met geavanceerde SQL-gyraties.
Mogelijke opties in kaart brengen, u staat niet alleen in het verzoek. Het onvermogen om lijsten in fragmenten te begrijpen (hetzij als onderdeel van order_by
of where
of waar dan ook) is genoemd in Ecto issue #1485
, op StackOverflow
, op het Elixir Forum
en deze blogbericht
. Vooral dat laatste is leerzaam. Daarover straks meer. Laten we eerst wat experimenten proberen.
Experiment #1: Men zou eerst kunnen proberen Kernel.apply/3
. te gebruiken om de lijst door te geven aan fragment
, maar dat werkt niet:
|> order_by(Kernel.apply(Ecto.Query.Builder, :fragment, ^ids))
Experiment #2: Dan kunnen we het misschien bouwen met stringmanipulatie. Hoe zit het met het geven van fragment
een tekenreeks die tijdens runtime is gebouwd met voldoende tijdelijke aanduidingen om uit de lijst te halen:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(Enum.map(ids, fn _ -> "?" end), ","), ")"], ""), ^ids))
Wat zou FIELD(id,?,?,?)
. opleveren gegeven ids = [1, 2, 3]
. Nee, dit werkt ook niet.
Experiment #3: De volledige, definitieve SQL maken die is opgebouwd uit de id's, waarbij de onbewerkte ID-waarden rechtstreeks in de samengestelde tekenreeks worden geplaatst. Behalve dat het verschrikkelijk is, werkt het ook niet:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(^ids, ","), ")"], "")))
Experiment #4: Dit brengt me bij die blogpost die ik noemde. Daarin hackt de auteur het ontbreken van or_where
met behulp van een set vooraf gedefinieerde macro's op basis van het aantal voorwaarden om samen te werken:
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
Hoewel dit werkt en de ORM "met de korrel" gebruikt om zo te zeggen, vereist het dat je een eindig, beheersbaar aantal beschikbare velden hebt. Dit kan al dan niet een game changer zijn.
Mijn aanbeveling:probeer niet te goochelen met de lekken van een ORM. U kent de beste vraag. Als de ORM het niet accepteert, schrijf het dan rechtstreeks met onbewerkte SQL en documenteer waarom de ORM niet werkt. Scherm het af achter een functie of module, zodat u zich het toekomstige recht voorbehoudt om de implementatie ervan te wijzigen. Op een dag, wanneer de ORM inhaalt, kun je het gewoon netjes herschrijven zonder gevolgen voor de rest van het systeem.