diff --git a/integration_test/cases/repo.exs b/integration_test/cases/repo.exs index 667db70a77..5b3e39360f 100644 --- a/integration_test/cases/repo.exs +++ b/integration_test/cases/repo.exs @@ -2207,6 +2207,17 @@ defmodule Ecto.Integration.RepoTest do assert TestRepo.all(query) == Enum.map(values, &{&1, &1.bid}) end + test "all with schema types" do + uuid_module = uuid_module(TestRepo.__adapter__()) + uuid = uuid_module.generate() + + raw_values = [%{bid: uuid, visits: "1"}, %{bid: uuid, visits: "2"}] + casted_values = [%{bid: uuid, visits: 1}, %{bid: uuid, visits: 2}] + types = Post + query = from v in values(raw_values, types) + assert TestRepo.all(query) == casted_values + end + test "all with join" do uuid_module = uuid_module(TestRepo.__adapter__()) uuid = uuid_module.generate() diff --git a/lib/ecto/query.ex b/lib/ecto/query.ex index 279448eb95..9d3ca7ac08 100644 --- a/lib/ecto/query.ex +++ b/lib/ecto/query.ex @@ -507,7 +507,7 @@ defmodule Ecto.Query do MapSet.to_list(fields) end - defp types!(fields, types) do + defp types!(fields, types) when is_map(types) do Enum.map(fields, fn field -> case types do %{^field => type} -> @@ -521,6 +521,18 @@ defmodule Ecto.Query do end) end + defp types!(fields, schema) when is_atom(schema) do + Enum.map(fields, fn field -> + if type = schema.__schema__(:type, field) do + {field, type} + else + raise ArgumentError, + "values/2 must declare the type for every field. " <> + "The type was not given for field `#{field}`" + end + end) + end + defp params!(values_list, types) do Enum.reduce(values_list, [], fn values, params -> Enum.reduce(types, params, fn {field, type}, params -> diff --git a/lib/ecto/query/api.ex b/lib/ecto/query/api.ex index 9d2c6ce540..4dfe57d9bd 100644 --- a/lib/ecto/query/api.ex +++ b/lib/ecto/query/api.ex @@ -518,13 +518,15 @@ defmodule Ecto.Query.API do An error is raised if the list is empty or if every map does not have exactly the same fields. - The second argument is a map of types corresponding to the fields in the first argument. + The second argument is either a map of types or an Ecto schema containing all the + fields in the first argument. + Each field must be given a type or an error is raised. Any type that can be specified in a schema may be used. Queries using a values list are not cacheable by Ecto. - ## Select example + ## Select with map types example values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}] types = %{id: :integer, text: :string} @@ -536,6 +538,18 @@ defmodule Ecto.Query.API do Repo.all(query) + ## Select with schema types example + + values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}] + types = ValuesSchema + + query = + from v1 in values(values, types), + join: v2 in values(values, types), + on: v1.id == v2.id + + Repo.all(query) + ## Delete example values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}] types = %{id: :integer, text: :string} diff --git a/test/ecto/query/builder/from_test.exs b/test/ecto/query/builder/from_test.exs index 8114e587d9..99ca8ca915 100644 --- a/test/ecto/query/builder/from_test.exs +++ b/test/ecto/query/builder/from_test.exs @@ -5,6 +5,16 @@ defmodule Ecto.Query.Builder.FromTest do import Ecto.Query + defmodule Schema do + use Ecto.Schema + + schema "schema" do + field :num, :integer + field :text, :string + end + end + + defmacro from_macro(left, right) do quote do fragment("? <> ?", unquote(left), unquote(right)) @@ -19,23 +29,32 @@ defmodule Ecto.Query.Builder.FromTest do end test "values list source" do - # Valid input values = [%{num: 1, text: "one"}, %{num: 2, text: "two"}] types = %{num: :integer, text: :string} query = from v in values(values, types) types_kw = Enum.map(types, & &1) assert query.from.source == {:values, [], [types_kw, length(values)]} + end + + test "values list source with types defined by schema" do + values = [%{num: 1, text: "one"}, %{num: 2, text: "two"}] + type_schema = Schema + types_kw = Enum.map(%{num: :integer, text: :string}, & &1) + query = from v in values(values, type_schema) + + assert query.from.source == {:values, [], [types_kw, length(values)]} + end - # Empty values + test "values list source with empty values" do msg = "must provide a non-empty list to values/2" assert_raise ArgumentError, msg, fn -> from v in values([], %{}) end + end - - # Missing type + test "values list source with missing types" do msg = "values/2 must declare the type for every field. The type was not given for field `text`" assert_raise ArgumentError, msg, fn -> @@ -43,7 +62,19 @@ defmodule Ecto.Query.Builder.FromTest do types = %{num: :integer} from v in values(values, types) end + end + + test "values list source with missing schema types" do + msg = "values/2 must declare the type for every field. The type was not given for field `not_a_field`" + + assert_raise ArgumentError, msg, fn -> + values = [%{not_a_field: 1}] + types = Schema + from v in values(values, types) + end + end + test "values list source with inconsistent fields across entries" do # Missing field msg = "each member of a values list must have the same fields. Missing field `text` in %{num: 2}"