Current section

Files

Jump to
quackdb lib quack_db ecto conditionals.ex
Raw

lib/quack_db/ecto/conditionals.ex

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
clauses = List.wrap(clauses)
{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
defp split_clauses!(clauses) do
parsed = Enum.map(clauses, &parse_clause!/1)
[last | rest_reversed] = Enum.reverse(parsed)
case last do
{:else, else_expression} -> {Enum.reverse(rest_reversed), else_expression}
_other -> raise ArgumentError, "case_when requires a final true -> expression clause"
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 clause: #{Macro.to_string(other)}"
end
end
end