diff --git a/lib/ecto/query/builder/windows.ex b/lib/ecto/query/builder/windows.ex index c5bfb930b6..9bd341aa2c 100644 --- a/lib/ecto/query/builder/windows.ex +++ b/lib/ecto/query/builder/windows.ex @@ -72,8 +72,21 @@ defmodule Ecto.Query.Builder.Windows do 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}`") + + defp escape_frame(other, params_acc, vars, env) do + macro_env = + case env do + {env, _} -> env + env -> env + end + + case Macro.expand_once(other, macro_env) do + ^other -> + Builder.error!("expected a dynamic or fragment in `:frame`, got: `#{inspect other}`") + + expanded -> + escape_frame(expanded, params_acc, vars, env) + end end defp error!(other) do diff --git a/test/ecto/query/builder/windows_test.exs b/test/ecto/query/builder/windows_test.exs index de2ea14713..162c335a55 100644 --- a/test/ecto/query/builder/windows_test.exs +++ b/test/ecto/query/builder/windows_test.exs @@ -6,6 +6,18 @@ defmodule Ecto.Query.Builder.WindowsTest do import Ecto.Query + defmacrop rows_between_preceding(count) do + quote do + fragment(unquote("ROWS BETWEEN #{count} PRECEDING AND CURRENT ROW")) + end + end + + defmacro invalid_frame do + quote do + [rows: -3, exclude: :current] + end + end + describe "escape" do test "handles expressions and params" do assert {Macro.escape(quote(do: [partition_by: [&0.y()]])), [], {[], %{}}} == @@ -63,6 +75,26 @@ defmodule Ecto.Query.Builder.WindowsTest do query = "q" |> windows([p], w: [frame: fragment("FOOBAR")]) assert query.windows[:w].expr[:frame] == {:fragment, [], [raw: "FOOBAR"]} end + + test "defines frame with macro that expands to fragment" do + query = "q" |> windows([p], w: [frame: rows_between_preceding(6)]) + + assert query.windows[:w].expr[:frame] == + {:fragment, [], [raw: "ROWS BETWEEN 6 PRECEDING AND CURRENT ROW"]} + end + + test "raises on frame macro that does not expand to fragment" do + assert_raise Ecto.Query.CompileError, ~r"expected a dynamic or fragment in `:frame`", fn -> + Code.eval_quoted( + quote do + import Ecto.Query + import unquote(__MODULE__) + + "q" |> windows([p], w: [frame: invalid_frame()]) + end + ) + end + end end describe "at runtime" do