8000 Fix macros inside of order_by by greg-rychlewski · Pull Request #4578 · elixir-ecto/ecto · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fix macros inside of order_by #4578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 14, 2025

Conversation

greg-rychlewski
Copy link
Member

Closes #4575

Copy link
Member
@josevalim josevalim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful!

@greg-rychlewski greg-rychlewski merged commit 1be980f into elixir-ecto:master Feb 14, 2025
7 checks passed
@Gigitsu
Copy link
Contributor
Gigitsu commented Jun 23, 2025

Hello,

Unfortunately, it seems that this PR may have introduced a regression.

Given the following setup:

defmodule MyQueryUtils do
  defmacro foo(table) do
    quote(do: unquote(table).priority > 0)
  end
end

defmodule Post do
  use Ecto.Schema

  @primary_key false
  schema "posts" do
    field :title, :string
    field :priority, :integer
  end
end

This query no longer compiles:

import MyQueryUtils
import Ecto.Query

select(Post, [o], %{row_number: over(row_number(), order_by: [desc: foo(o)])})

With the following error:

** (KeyError) key :module not found in: {#Macro.Env<
   aliases: [],
   ...
 >, #Function<4.25497058/5 in Ecto.Query.Builder.Select.escape_expansion>}

If you are using the dot syntax, such as map.field, make sure the left-hand side of the dot is a map
    (elixir 1.18.3) lib/macro/env.ex:539: Macro.Env.expand_import/5
    (elixir 1.18.3) lib/macro.ex:1877: Macro.do_expand_once/2
    (elixir 1.18.3) lib/macro.ex:1824: Macro.expand_once/2
    (ecto 3.13.1) lib/ecto/query/builder.ex:1101: Ecto.Query.Builder.try_expansion/5
    (ecto 3.13.1) lib/ecto/query/builder/order_by.ex:79: Ecto.Query.Builder.OrderBy.do_escape/5
    (elixir 1.18.3) lib/enum.ex:1316: anonymous fn/3 in Enum.flat_map_reduce/3
    (elixir 1.18.3) lib/enum.ex:4968: Enumerable.List.reduce/3
    (elixir 1.18.3) lib/enum.ex:1315: Enum.flat_map_reduce/3

A possible fix could be to handle this specific case here by adding:

defp do_escape(expr, params_acc, kind, vars, {env, _}) do
  do_escape(expr, params_acc, kind, vars, env)
end

Let me know what you think, I’d be happy to help test and open a pull request for this.

Thanks again!

Edit:

I've simplified the test query

@greg-rychlewski
Copy link
Member Author

@Gigitsu Thanks for finding this. I think it might be a bit safer to restrict it to the places where we are setting {env, fun} ourselves when calling the builder. In case the code changes in the future and we want to pass a callback coming from somewhere else.

But besides that what you said looks like the right fix to me!

@Gigitsu
Copy link
Contributor
Gigitsu commented Jun 23, 2025

I think it might be a bit safer to restrict it to the places where we are setting {env, fun} ourselves when calling the builder.

I’m not sure I’ve understood correctly.

Do you mean something like this?

  defp do_escape({dir, expr}, params_acc, kind, vars, env) do
    env = with {env, _} <- env, do: env
    fun = &escape_expansion(kind, &1, &2, &3, &4, &5)
    {ast, params_acc} = Builder.escape(expr, :any, params_acc, vars, {env, fun})
    {[{quoted_dir!(kind, dir), ast}], params_acc}
  end

  defp do_escape(expr, params_acc, kind, vars, env) do
    env = with {env, _} <- env, do: env
    fun = &escape_expansion(kind, &1, &2, &3, &4, &5)
    {ast, params_acc} = Builder.escape(expr, :any, params_acc, vars, {env, fun})

    if is_list(ast) do
      {ast, params_acc}
    else
      {[{:asc, ast}], params_acc}
    end
  end

@greg-rychlewski
Copy link
Member Author

@Gigitsu Yeah basically that. In other places like the main builder function we define a get_env helper like this that might be a bit easier to read

defp get_env({env, _}), do: env
  defp get_env(env), do: env

@Gigitsu
Copy link
Contributor
Gigitsu commented Jun 23, 2025

@greg-rychlewski I've created the #4630 pull request.

I'm unsure about the test setup. Specifically, I'm not confident whether importing macros from BuilderTest and requiring the file with Code.require_file("../builder_test.exs", __DIR__) is the right approach.

If you agree, we can move the discussion to the pull request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

is_nil not allowed in order_by since v3.12.0
3 participants
0