defmodule ExpressionToEcto do import Ecto.Query def to_dynamic([]), do: true def to_dynamic([single]), do: build_dynamic(single) def to_dynamic(ast), do: build_dynamic(ast) # Operadores lógicos defp build_dynamic({:or, left, right}) do dynamic([q], ^build_dynamic(left) or ^build_dynamic(right)) end defp build_dynamic({:and, left, right}) do dynamic([q], ^build_dynamic(left) and ^build_dynamic(right)) end # Comparaciones con número defp build_dynamic({:comparison, [{:field, field}, op, {:number, val}]}) do field_atom = String.to_atom(field) case op do :== -> dynamic([q], field(q, ^field_atom) == ^val) :!= -> dynamic([q], field(q, ^field_atom) != ^val) :> -> dynamic([q], field(q, ^field_atom) > ^val) :< -> dynamic([q], field(q, ^field_atom) < ^val) :>= -> dynamic([q], field(q, ^field_atom) >= ^val) :<= -> dynamic([q], field(q, ^field_atom) <= ^val) other -> raise "Operador no soportado: #{inspect(other)}" end end # Comparaciones con string defp build_dynamic({:comparison, [{:field, field}, op, {:string, val}]}) when op in [:==, :!=] do field_atom = String.to_atom(field) case op do :== -> dynamic([q], field(q, ^field_atom) == ^val) :!= -> dynamic([q], field(q, ^field_atom) != ^val) end end # ILIKE defp build_dynamic({:comparison, [{:field, field}, :ilike, {:string, val}]}) do field_atom = String.to_atom(field) pattern = "#{val}" dynamic([q], ilike(field(q, ^field_atom), ^pattern)) end # IN defp build_dynamic({:comparison, [{:field, field}, :in, {:list, vals}]}) when is_list(vals) do field_atom = String.to_atom(field) parsed_vals = Enum.map(vals, fn {:string, val} -> val {:number, val} -> val other -> raise "Tipo de dato no soportado en IN: #{inspect(other)}" end) dynamic([q], field(q, ^field_atom) in ^parsed_vals) end defp build_dynamic(other) do raise "AST no soportado o mal formado: #{inspect(other)}" end end