if Code.ensure_loaded?(Ecto.Query.API) do
defmodule QuackDB.Ecto.Conditionals do
@moduledoc """
Conditional expression helpers for Ecto analytical queries.
`case_when/1` keeps multi-branch SQL `CASE WHEN` expressions readable while
preserving normal Ecto operators inside each condition:
use QuackDB.Ecto
from event in "events",
select: %{
tier:
case_when do
event.score >= 90 -> "high"
event.score >= 50 -> "medium"
true -> "low"
end
}
The expression compiles to DuckDB `CASE WHEN ... THEN ... ELSE ... END`.
"""
defmacro case_when(do: clauses) do
{when_clauses, else_expression} = __split_clauses__!(clauses)
sql =
["CASE ", Enum.map(when_clauses, fn _ -> "WHEN ? THEN ? " end), "ELSE ? END"]
|> IO.iodata_to_binary()
args =
when_clauses
|> Enum.flat_map(fn {condition, expression} -> [condition, expression] end)
|> Kernel.++([else_expression])
quote do
fragment(unquote(sql), unquote_splicing(args))
end
end
@doc false
def __split_clauses__!(clauses) do
clauses = List.wrap(clauses)
parsed = Enum.map(clauses, &parse_clause!/1)
case Enum.reverse(parsed) do
[{:else, else_expression} | rest_reversed] ->
{Enum.reverse(rest_reversed), else_expression}
_other ->
raise ArgumentError, "expected case_when/1 to end with `true -> expression`"
end
end
defp parse_clause!({:->, _meta, [[true], expression]}), do: {:else, expression}
defp parse_clause!({:->, _meta, [[condition], expression]}), do: {condition, expression}
defp parse_clause!(other) do
raise ArgumentError, "invalid case_when/1 clause: #{Macro.to_string(other)}"
end
end
end