if Code.ensure_loaded?(Ecto.Query.API) do
defmodule QuackDB.Ecto.WindowFrames do
@moduledoc """
DuckDB window-frame helpers for Ecto queries.
Ecto versions before the release containing
[ecto#4730](https://github.com/elixir-ecto/ecto/pull/4730) only accept literal
`fragment(...)` calls in a window `:frame` option. These helpers are ready for
that Ecto release; until your project depends on it, use the generated SQL
shape directly:
frame: fragment("ROWS BETWEEN 6 PRECEDING AND CURRENT ROW")
Once macro-expanded frames are available in your Ecto version, helpers can be
used as:
frame: rows_between({:preceding, 6}, :current_row)
frame: range_between(:unbounded_preceding, {:following, 1})
"""
@doc "Builds a `ROWS BETWEEN ... AND ...` window frame fragment."
defmacro rows_between(start_bound, end_bound) do
frame_fragment("ROWS", start_bound, end_bound)
end
@doc "Builds a `RANGE BETWEEN ... AND ...` window frame fragment."
defmacro range_between(start_bound, end_bound) do
frame_fragment("RANGE", start_bound, end_bound)
end
@doc "Builds a `GROUPS BETWEEN ... AND ...` window frame fragment."
defmacro groups_between(start_bound, end_bound) do
frame_fragment("GROUPS", start_bound, end_bound)
end
defp frame_fragment(unit, start_bound, end_bound) do
sql = [unit, " BETWEEN ", frame_bound!(start_bound), " AND ", frame_bound!(end_bound)]
sql = IO.iodata_to_binary(sql)
quote do
fragment(unquote(sql))
end
end
defp frame_bound!(:unbounded_preceding), do: "UNBOUNDED PRECEDING"
defp frame_bound!(:unbounded_following), do: "UNBOUNDED FOLLOWING"
defp frame_bound!(:current_row), do: "CURRENT ROW"
defp frame_bound!({:preceding, count}) when is_integer(count) and count >= 0,
do: [Integer.to_string(count), " PRECEDING"]
defp frame_bound!({:following, count}) when is_integer(count) and count >= 0,
do: [Integer.to_string(count), " FOLLOWING"]
defp frame_bound!(bound) do
raise ArgumentError,
"unsupported window frame bound #{inspect(bound)}; expected :unbounded_preceding, " <>
":unbounded_following, :current_row, {:preceding, non_neg_integer}, or " <>
"{:following, non_neg_integer}"
end
end
end