Packages
ecto
3.4.3
3.14.1
3.14.0
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.2
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.4
3.8.3
3.8.2
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.1
3.0.0-rc.0
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.2.0-rc.1
2.2.0-rc.0
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.1.0-rc.5
2.1.0-rc.4
2.1.0-rc.3
2.1.0-rc.2
2.1.0-rc.1
2.1.0-rc.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-beta.2
2.0.0-beta.1
2.0.0-beta.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.12.0-rc
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
A toolkit for data mapping and language integrated query for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/query/builder/windows.ex
import Kernel, except: [apply: 2]
defmodule Ecto.Query.Builder.Windows do
@moduledoc false
alias Ecto.Query.Builder
alias Ecto.Query.Builder.{GroupBy, OrderBy}
@sort_order [:partition_by, :order_by, :frame]
@doc """
Escapes a window params.
## Examples
iex> escape(quote do [order_by: [desc: 13]] end, {[], :acc}, [x: 0], __ENV__)
{[order_by: [desc: 13]], [], {[], :acc}}
"""
@spec escape([Macro.t], {list, term}, Keyword.t, Macro.Env.t | {Macro.Env.t, fun})
:: {Macro.t, [{atom, term}], {list, term}}
def escape(kw, params_acc, vars, env) when is_list(kw) do
{compile, runtime} = sort(@sort_order, kw, :compile, [], [])
{compile, params_acc} = Enum.map_reduce(compile, params_acc, &escape_compile(&1, &2, vars, env))
{compile, runtime, params_acc}
end
def escape(kw, _params_acc, _vars, _env) do
error!(kw)
end
defp sort([key | keys], kw, mode, compile, runtime) do
case Keyword.pop(kw, key) do
{nil, kw} ->
sort(keys, kw, mode, compile, runtime)
{{:^, _, [var]}, kw} ->
sort(keys, kw, :runtime, compile, [{key, var} | runtime])
{_, _} when mode == :runtime ->
[{runtime_key, _} | _] = runtime
raise ArgumentError, "window has an interpolated value under `#{runtime_key}` " <>
"and therefore `#{key}` must also be interpolated"
{expr, kw} ->
sort(keys, kw, mode, [{key, expr} | compile], runtime)
end
end
defp sort([], [], _mode, compile, runtime) do
{Enum.reverse(compile), Enum.reverse(runtime)}
end
defp sort([], kw, _mode, _compile, _runtime) do
error!(kw)
end
defp escape_compile({:partition_by, fields}, params_acc, vars, env) do
{fields, params_acc} = GroupBy.escape(:partition_by, fields, params_acc, vars, env)
{{:partition_by, fields}, params_acc}
end
defp escape_compile({:order_by, fields}, params_acc, vars, env) do
{fields, params_acc} = OrderBy.escape(:order_by, fields, params_acc, vars, env)
{{:order_by, fields}, params_acc}
end
defp escape_compile({:frame, frame_clause}, params_acc, vars, env) do
{frame_clause, params_acc} = escape_frame(frame_clause, params_acc, vars, env)
{{:frame, frame_clause}, params_acc}
end
defp escape_frame({:fragment, _, _} = fragment, params_acc, vars, env) do
Builder.escape(fragment, :any, params_acc, vars, env)
end
defp escape_frame(other, _, _, _) do
Builder.error!("expected a dynamic or fragment in `:frame`, got: `#{inspect other}`")
end
defp error!(other) do
Builder.error!(
"expected window definition to be a keyword list " <>
"with partition_by, order_by or frame as keys, got: `#{inspect other}`"
)
end
@doc """
Builds a quoted expression.
The quoted expression should evaluate to a query at runtime.
If possible, it does all calculations at compile time to avoid
runtime work.
"""
@spec build(Macro.t, [Macro.t], Keyword.t, Macro.Env.t) :: Macro.t
def build(query, binding, windows, env) when is_list(windows) do
{query, binding} = Builder.escape_binding(query, binding, env)
{compile, runtime} =
windows
|> Enum.map(&escape_window(binding, &1, env))
|> Enum.split_with(&elem(&1, 2) == [])
compile = Enum.map(compile, &build_compile_window(&1, env))
runtime = Enum.map(runtime, &build_runtime_window(&1, env))
query = Builder.apply_query(query, __MODULE__, [compile], env)
if runtime == [] do
query
else
quote do
Ecto.Query.Builder.Windows.runtime!(
unquote(query),
unquote(runtime),
unquote(env.file),
unquote(env.line)
)
end
end
end
def build(_, _, windows, _) do
Builder.error!(
"expected window definitions to be a keyword list with window names as keys and " <>
"a keyword list with the window definition as value, got: `#{inspect windows}`"
)
end
defp escape_window(vars, {name, expr}, env) do
{compile_acc, runtime_acc, {params, _}} = escape(expr, {[], :acc}, vars, env)
{name, compile_acc, runtime_acc, Builder.escape_params(params)}
end
defp build_compile_window({name, compile_acc, _, params}, env) do
{name,
quote do
%Ecto.Query.QueryExpr{
expr: unquote(compile_acc),
params: unquote(params),
file: unquote(env.file),
line: unquote(env.line)
}
end}
end
defp build_runtime_window({name, compile_acc, runtime_acc, params}, _env) do
{:{}, [], [name, Enum.reverse(compile_acc), runtime_acc, Enum.reverse(params)]}
end
@doc """
Invoked for runtime windows.
"""
def runtime!(query, runtime, file, line) do
windows =
Enum.map(runtime, fn {name, compile_acc, runtime_acc, params} ->
{acc, params} = do_runtime_window!(runtime_acc, query, compile_acc, params)
expr = %Ecto.Query.QueryExpr{expr: Enum.reverse(acc), params: Enum.reverse(params), file: file, line: line}
{name, expr}
end)
apply(query, windows)
end
defp do_runtime_window!([{:order_by, order_by} | kw], query, acc, params) do
{order_by, params} = OrderBy.order_by_or_distinct!(:order_by, query, order_by, params)
do_runtime_window!(kw, query, [{:order_by, order_by} | acc], params)
end
defp do_runtime_window!([{:partition_by, partition_by} | kw], query, acc, params) do
{partition_by, params} = GroupBy.group_or_partition_by!(:partition_by, query, partition_by, params)
do_runtime_window!(kw, query, [{:partition_by, partition_by} | acc], params)
end
defp do_runtime_window!([{:frame, frame} | kw], query, acc, params) do
case frame do
%Ecto.Query.DynamicExpr{} ->
{frame, params, _count} = Builder.Dynamic.partially_expand(query, frame, params, length(params))
do_runtime_window!(kw, query, [{:frame, frame} | acc], params)
_ ->
raise ArgumentError,
"expected a dynamic or fragment in `:frame`, got: `#{inspect frame}`"
end
end
defp do_runtime_window!([], _query, acc, params), do: {acc, params}
@doc """
The callback applied by `build/4` to build the query.
"""
@spec apply(Ecto.Queryable.t, Keyword.t) :: Ecto.Query.t
def apply(%Ecto.Query{windows: windows} = query, definitions) do
merged = Keyword.merge(windows, definitions, fn name, _, _ ->
Builder.error! "window with name #{name} is already defined"
end)
%{query | windows: merged}
end
def apply(query, definitions) do
apply(Ecto.Queryable.to_query(query), definitions)
end
end