if Code.ensure_loaded?(Ecto.Query.API) do defmodule QuackDB.Ecto.Star do @moduledoc """ DuckDB star and `COLUMNS(...)` expression macros for Ecto SQL generation. These helpers wrap `QuackDB.SQL.star/1`, `QuackDB.SQL.columns/1,2`, and `QuackDB.SQL.unpack_columns/1,2` in Ecto fragments. DuckDB star expressions can expand one Ecto expression into multiple SQL result columns. That is useful for SQL generation and raw `Repo.query!/2` execution, but it can surprise Ecto's normal result loader in `Repo.all/2` select lists. Prefer these macros in predicates or when you intentionally pass generated SQL to `Repo.query!/2`. """ @doc "Builds a DuckDB star expression fragment." defmacro star(options \\ []) do fragment_sql = options |> QuackDB.SQL.star() |> IO.iodata_to_binary() quote do fragment(unquote(fragment_sql)) end end @doc false def __selector_param__(selector) when is_list(selector), do: Enum.map(selector, &to_string/1) def __selector_param__(selector) when is_binary(selector), do: selector @doc "Builds a DuckDB `COLUMNS(...)` expression fragment." defmacro columns(selector \\ :star, options \\ []) defmacro columns({:^, meta, [selector]}, []) do pinned_selector = {:^, meta, [{{:., [], [__MODULE__, :__selector_param__]}, [], [selector]}]} quote do fragment("COLUMNS(?)", unquote(pinned_selector)) end end defmacro columns({:^, _meta, [_selector]}, options) do raise ArgumentError, "dynamic COLUMNS selectors cannot be combined with star options: #{inspect(options)}" end defmacro columns(selector, options) do fragment_sql = selector |> QuackDB.SQL.columns(options) |> IO.iodata_to_binary() quote do fragment(unquote(fragment_sql)) end end @doc "Builds a DuckDB `*COLUMNS(...)` unpacked columns expression fragment." defmacro unpack_columns(selector \\ :star, options \\ []) defmacro unpack_columns({:^, meta, [selector]}, []) do pinned_selector = {:^, meta, [{{:., [], [__MODULE__, :__selector_param__]}, [], [selector]}]} quote do fragment("*COLUMNS(?)", unquote(pinned_selector)) end end defmacro unpack_columns({:^, _meta, [_selector]}, options) do raise ArgumentError, "dynamic *COLUMNS selectors cannot be combined with star options: #{inspect(options)}" end defmacro unpack_columns(selector, options) do fragment_sql = selector |> QuackDB.SQL.unpack_columns(options) |> IO.iodata_to_binary() quote do fragment(unquote(fragment_sql)) end end end end