Building dynamic queries in Ecto

What is the proper way of building a dynamic Ecto query where the fields to be compared are determined dynamically? E.g. suppose we have a table with 4 fields (“a”, “b”, “c”, “d”), and the build_query function receives a candidate record which is a map optionally containing values for each field. If a field is found in the record parameter, I’d like to include it in the “where” expression of the query. However, it’s not clear how to define the field reference in the query dynamically (i.e. “q.^f = ^v”) below:

def build_query(record) do
  where =
    Enum.filter(["a", "b", "c", "d"], & Map.get(record, &1) != nil)
    |> Enum.reduce(false, fn(x, acc) ->
                            v = Map.get(record, x)
                            f = String.to_existing_atom(x)
                            dynamic([q], q.^f == ^v or ^acc)
                          end)
   from q in SomeTable, ^where
end

The closest solution I can think of is to do this:

def build_query(record) do
  build = fn
    ("a", val, acc) -> dynamic([q], q.a == ^val or ^acc)
    ("b", val, acc) -> dynamic([q], q.b == ^val or ^acc)
    ("c", val, acc) -> dynamic([q], q.c == ^val or ^acc)
    ("d", val, acc) -> dynamic([q], q.d == ^val or ^acc)
  end
  where = Enum.filter(["a", "b", "c", "d"], & Map.get(record, &1) != nil)
       |> Enum.reduce(false, fn (x, acc) -> build(x, record[x], acc) end)
  from q in SomeTable, ^where
end

but I wonder if there’s a way to get rid of the need to explicitly code all the choices in the lambda above?

Does dynamic accept Ecto.Query.API — Ecto v3.7.1 ?

Oh, that’s it, thank you! So the query would look like this:

def build_query(record) do
  where =
    Enum.filter(["a", "b", "c", "d"], & Map.get(record, &1) != nil)
    |> Enum.reduce(false, fn(x, acc) ->
                            v = Map.get(record, x)
                            f = String.to_existing_atom(x)
                            dynamic([q], field(q, ^f) == ^v or ^acc)
                          end)
  from q in SomeTable, ^where
end