Skip to content

Commit 38453a4

Browse files
Allow schema to be used for values list types (#4553)
1 parent 4d3c5ee commit 38453a4

4 files changed

Lines changed: 75 additions & 7 deletions

File tree

integration_test/cases/repo.exs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,6 +2207,17 @@ defmodule Ecto.Integration.RepoTest do
22072207
assert TestRepo.all(query) == Enum.map(values, &{&1, &1.bid})
22082208
end
22092209

2210+
test "all with schema types" do
2211+
uuid_module = uuid_module(TestRepo.__adapter__())
2212+
uuid = uuid_module.generate()
2213+
2214+
raw_values = [%{bid: uuid, visits: "1"}, %{bid: uuid, visits: "2"}]
2215+
casted_values = [%{bid: uuid, visits: 1}, %{bid: uuid, visits: 2}]
2216+
types = Post
2217+
query = from v in values(raw_values, types)
2218+
assert TestRepo.all(query) == casted_values
2219+
end
2220+
22102221
test "all with join" do
22112222
uuid_module = uuid_module(TestRepo.__adapter__())
22122223
uuid = uuid_module.generate()

lib/ecto/query.ex

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ defmodule Ecto.Query do
507507
MapSet.to_list(fields)
508508
end
509509

510-
defp types!(fields, types) do
510+
defp types!(fields, types) when is_map(types) do
511511
Enum.map(fields, fn field ->
512512
case types do
513513
%{^field => type} ->
@@ -521,6 +521,18 @@ defmodule Ecto.Query do
521521
end)
522522
end
523523

524+
defp types!(fields, schema) when is_atom(schema) do
525+
Enum.map(fields, fn field ->
526+
if type = schema.__schema__(:type, field) do
527+
{field, type}
528+
else
529+
raise ArgumentError,
530+
"values/2 must declare the type for every field. " <>
531+
"The type was not given for field `#{field}`"
532+
end
533+
end)
534+
end
535+
524536
defp params!(values_list, types) do
525537
Enum.reduce(values_list, [], fn values, params ->
526538
Enum.reduce(types, params, fn {field, type}, params ->

lib/ecto/query/api.ex

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,13 +518,15 @@ defmodule Ecto.Query.API do
518518
An error is raised if the list is empty or if every map does not have exactly the
519519
same fields.
520520
521-
The second argument is a map of types corresponding to the fields in the first argument.
521+
The second argument is either a map of types or an Ecto schema containing all the
522+
fields in the first argument.
523+
522524
Each field must be given a type or an error is raised. Any type that can be specified in
523525
a schema may be used.
524526
525527
Queries using a values list are not cacheable by Ecto.
526528
527-
## Select example
529+
## Select with map types example
528530
529531
values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}]
530532
types = %{id: :integer, text: :string}
@@ -536,6 +538,18 @@ defmodule Ecto.Query.API do
536538
537539
Repo.all(query)
538540
541+
## Select with schema types example
542+
543+
values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}]
544+
types = ValuesSchema
545+
546+
query =
547+
from v1 in values(values, types),
548+
join: v2 in values(values, types),
549+
on: v1.id == v2.id
550+
551+
Repo.all(query)
552+
539553
## Delete example
540554
values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}]
541555
types = %{id: :integer, text: :string}

test/ecto/query/builder/from_test.exs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ defmodule Ecto.Query.Builder.FromTest do
55

66
import Ecto.Query
77

8+
defmodule Schema do
9+
use Ecto.Schema
10+
11+
schema "schema" do
12+
field :num, :integer
13+
field :text, :string
14+
end
15+
end
16+
17+
818
defmacro from_macro(left, right) do
919
quote do
1020
fragment("? <> ?", unquote(left), unquote(right))
@@ -19,31 +29,52 @@ defmodule Ecto.Query.Builder.FromTest do
1929
end
2030

2131
test "values list source" do
22-
# Valid input
2332
values = [%{num: 1, text: "one"}, %{num: 2, text: "two"}]
2433
types = %{num: :integer, text: :string}
2534
query = from v in values(values, types)
2635

2736
types_kw = Enum.map(types, & &1)
2837
assert query.from.source == {:values, [], [types_kw, length(values)]}
38+
end
39+
40+
test "values list source with types defined by schema" do
41+
values = [%{num: 1, text: "one"}, %{num: 2, text: "two"}]
42+
type_schema = Schema
43+
types_kw = Enum.map(%{num: :integer, text: :string}, & &1)
44+
query = from v in values(values, type_schema)
45+
46+
assert query.from.source == {:values, [], [types_kw, length(values)]}
47+
end
2948

30-
# Empty values
49+
test "values list source with empty values" do
3150
msg = "must provide a non-empty list to values/2"
3251

3352
assert_raise ArgumentError, msg, fn ->
3453
from v in values([], %{})
3554
end
55+
end
3656

37-
38-
# Missing type
57+
test "values list source with missing types" do
3958
msg = "values/2 must declare the type for every field. The type was not given for field `text`"
4059

4160
assert_raise ArgumentError, msg, fn ->
4261
values = [%{num: 1, text: "one"}, %{num: 2, text: "two"}]
4362
types = %{num: :integer}
4463
from v in values(values, types)
4564
end
65+
end
66+
67+
test "values list source with missing schema types" do
68+
msg = "values/2 must declare the type for every field. The type was not given for field `not_a_field`"
69+
70+
assert_raise ArgumentError, msg, fn ->
71+
values = [%{not_a_field: 1}]
72+
types = Schema
73+
from v in values(values, types)
74+
end
75+
end
4676

77+
test "values list source with inconsistent fields across entries" do
4778
# Missing field
4879
msg = "each member of a values list must have the same fields. Missing field `text` in %{num: 2}"
4980

0 commit comments

Comments
 (0)