Skip to content

Commit a4644cb

Browse files
authored
Suggest alternatives to inexistent fields in queries (#3754)
Looks for similar fields and tries to suggest them when raising about invalid query.
1 parent 3785282 commit a4644cb

File tree

3 files changed

+40
-5
lines changed

3 files changed

+40
-5
lines changed

lib/ecto/exceptions.ex

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ defmodule Ecto.QueryError do
2828
def exception(opts) do
2929
message = Keyword.fetch!(opts, :message)
3030
query = Keyword.fetch!(opts, :query)
31-
hint = Keyword.fetch(opts, :hint)
31+
hint = Keyword.get(opts, :hint)
3232

3333
message = """
3434
#{message} in query:
@@ -48,9 +48,10 @@ defmodule Ecto.QueryError do
4848
end
4949

5050
message =
51-
case hint do
52-
{:ok, text} -> message <> "\n" <> text <> "\n"
53-
_ -> message
51+
if hint do
52+
message <> "\n" <> hint <> "\n"
53+
else
54+
message
5455
end
5556

5657
%__MODULE__{message: message}

lib/ecto/query/planner.ex

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1737,7 +1737,31 @@ defmodule Ecto.Query.Planner do
17371737
error! query, expr, "field `#{field}` in `#{kind}` is a virtual field in schema #{inspect schema}"
17381738

17391739
true ->
1740-
error! query, expr, "field `#{field}` in `#{kind}` does not exist in schema #{inspect schema}"
1740+
hint = closest_fields_hint(field, schema)
1741+
error! query, expr, "field `#{field}` in `#{kind}` does not exist in schema #{inspect schema}", hint
1742+
end
1743+
end
1744+
1745+
defp closest_fields_hint(input, schema) do
1746+
input_string = Atom.to_string(input)
1747+
1748+
schema.__schema__(:fields)
1749+
|> Enum.map(fn field -> {field, String.jaro_distance(input_string, Atom.to_string(field))} end)
1750+
|> Enum.filter(fn {_field, score} -> score >= 0.77 end)
1751+
|> Enum.sort(& elem(&1, 0) >= elem(&2, 0))
1752+
|> Enum.take(5)
1753+
|> Enum.map(&elem(&1, 0))
1754+
|> case do
1755+
[] ->
1756+
nil
1757+
1758+
[suggestion] ->
1759+
"Did you mean `#{suggestion}`?"
1760+
1761+
suggestions ->
1762+
Enum.reduce(suggestions, "Did you mean one of: \n", fn suggestion, acc ->
1763+
acc <> "\n * `#{suggestion}`"
1764+
end)
17411765
end
17421766
end
17431767

test/ecto/query/planner_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,16 @@ defmodule Ecto.Query.PlannerTest do
981981
normalize(query)
982982
end
983983

984+
exception = assert_raise Ecto.QueryError, fn ->
985+
query = from(Comment, []) |> select([c], c.postd)
986+
normalize(query)
987+
end
988+
989+
assert exception.message =~ "field `postd` in `select` does not exist in schema"
990+
assert exception.message =~ "Did you mean one of:"
991+
assert exception.message =~ "* `posted`"
992+
assert exception.message =~ "* `post_id`"
993+
984994
message = ~r"field `temp` in `select` is a virtual field in schema Ecto.Query.PlannerTest.Comment"
985995
assert_raise Ecto.QueryError, message, fn ->
986996
query = from(Comment, []) |> select([c], c.temp)

0 commit comments

Comments
 (0)