if Code.ensure_loaded?(Ecto.Query.API) do defmodule QuackDB.Ecto.Text do @moduledoc """ DuckDB text-expression helpers for Ecto queries. import Ecto.Query import QuackDB.Ecto.Text from event in "events", where: contains(event.name, "duck") and starts_with(event.kind, "bird"), select: %{ second_part: split_part(event.name, "-", 2), tags: string_split(event.tags, ",") } `contains_text/2` emits the same DuckDB `contains(?, ?)` call as `contains/2`. It is useful with `use QuackDB.Ecto`, where shared `contains/2` may dispatch between text and spatial predicates. """ @text_helpers [ %{name: :contains, sql: "contains", arities: [2]}, %{name: :contains_text, sql: "contains", arities: [2]}, %{name: :starts_with, arities: [2]}, %{name: :ends_with, arities: [2]}, %{name: :prefix, arities: [2]}, %{name: :suffix, arities: [2]}, %{name: :split_part, arities: [3]}, %{name: :string_split, arities: [2]}, %{name: :string_split_regex, arities: [2, 3]} ] @doc false def __text_helpers__, do: @text_helpers for %{name: name, arities: arities} = helper <- @text_helpers, arity <- arities do sql = Map.get(helper, :sql, Atom.to_string(name)) arguments = Macro.generate_arguments(arity, __MODULE__) fragment_sql = IO.iodata_to_binary([ sql, "(", Enum.map_join(1..arity, ", ", fn _ -> "?" end), ")" ]) defmacro unquote(name)(unquote_splicing(arguments)) do text_fragment(unquote(fragment_sql), [unquote_splicing(arguments)]) end end defp text_fragment(sql, arguments) do quote do fragment(unquote(sql), unquote_splicing(arguments)) end end end end