You should be able to use a combination of dynamic and field to achieve what you need.
There may be other ways? But most of my apps have tooling around this to achieve “segmentation” of a dynamic nature.
Would look like this:
import Ecto.Query
filtered_params = %{foo: "foo", bar: "bar"}
Enum.reduce(filtered_params, MySchema, fn {key, val}, queryable ->
where(queryable, ^dynamic([m], field(m, ^key) == ^val))
end)