From 4757cb50a9a1f3caee70a157ec7e9d6bcaf3a2b2 Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Wed, 21 Jun 2023 14:41:01 +0600 Subject: [PATCH 1/5] Allow virtual embeds --- lib/ecto/embedded.ex | 19 +++++++++++++++---- lib/ecto/schema.ex | 4 ++-- test/ecto/embedded_test.exs | 3 ++- test/ecto/repo_test.exs | 9 +++++++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/lib/ecto/embedded.ex b/lib/ecto/embedded.ex index e8c5fb28fb..072e790cbf 100644 --- a/lib/ecto/embedded.ex +++ b/lib/ecto/embedded.ex @@ -28,6 +28,7 @@ defmodule Ecto.Embedded do :owner, :related, :on_cast, + virtual: false, on_replace: :raise, unique: true, ordered: true @@ -149,7 +150,11 @@ defmodule Ecto.Embedded do @doc false def prepare(changeset, embeds, adapter, repo_action) do %{changes: changes, types: types, repo: repo} = changeset - prepare(Map.take(changes, embeds), types, adapter, repo, repo_action) + #non_virtual_embeds = Enum.reject(embeds, & &1.virtual) + + changes + |> Map.take(embeds) + |> prepare(types, adapter, repo, repo_action) end defp prepare(embeds, _types, _adapter, _repo, _repo_action) when embeds == %{} do @@ -158,9 +163,15 @@ defmodule Ecto.Embedded do defp prepare(embeds, types, adapter, repo, repo_action) do Enum.reduce(embeds, embeds, fn {name, changeset_or_changesets}, acc -> - {:embed, embed} = Map.get(types, name) - Map.put(acc, name, prepare_each(embed, changeset_or_changesets, adapter, repo, repo_action)) - end) + case Map.get(types, name) do + {:embed, %{virtual: false} = embed} -> + prepared = prepare_each(embed, changeset_or_changesets, adapter, repo, repo_action) + Map.put(acc, name, prepared) + + {:embed, %{virtual: true}} -> + acc + end) + end end defp prepare_each(%{cardinality: :one}, nil, _adapter, _repo, _repo_action) do diff --git a/lib/ecto/schema.ex b/lib/ecto/schema.ex index 1c723622bf..88b3e77176 100644 --- a/lib/ecto/schema.ex +++ b/lib/ecto/schema.ex @@ -2143,7 +2143,7 @@ defmodule Ecto.Schema do Module.put_attribute(mod, :ecto_changeset_fields, {name, {:assoc, struct}}) end - @valid_embeds_one_options [:on_replace, :source, :load_in_query, :defaults_to_struct] + @valid_embeds_one_options [:on_replace, :source, :load_in_query, :defaults_to_struct, :virtual] @doc false def __embeds_one__(mod, name, schema, opts) when is_atom(schema) do @@ -2164,7 +2164,7 @@ defmodule Ecto.Schema do "`embeds_one/3` expects `schema` to be a module name, but received #{inspect(schema)}" end - @valid_embeds_many_options [:on_replace, :source, :load_in_query] + @valid_embeds_many_options [:on_replace, :source, :load_in_query, :virtual] @doc false def __embeds_many__(mod, name, schema, opts) when is_atom(schema) do diff --git a/test/ecto/embedded_test.exs b/test/ecto/embedded_test.exs index 162107b6c2..4ad1d1cb98 100644 --- a/test/ecto/embedded_test.exs +++ b/test/ecto/embedded_test.exs @@ -19,6 +19,7 @@ defmodule Ecto.EmbeddedTest do field :name, :string embeds_one :profile, Profile, on_replace: :delete embeds_one :post, Post + embeds_one :virtual_post, Post, virtual: true embeds_many :posts, Post, on_replace: :delete end end @@ -45,7 +46,7 @@ defmodule Ecto.EmbeddedTest do test "__schema__" do assert Author.__schema__(:embeds) == - [:profile, :post, :posts] + [:profile, :post, :virtual_post, :posts] assert Author.__schema__(:embed, :profile) == %Embedded{field: :profile, cardinality: :one, owner: Author, on_replace: :delete, related: Profile} diff --git a/test/ecto/repo_test.exs b/test/ecto/repo_test.exs index 82f5bd99d8..5bf658cfc9 100644 --- a/test/ecto/repo_test.exs +++ b/test/ecto/repo_test.exs @@ -160,6 +160,15 @@ defmodule Ecto.RepoTest do end end + defmodule MySchemaVirtualEmbed do + use Ecto.Schema + + schema "my_schema" do + field :x, :string + embeds_one :virtual_embed, MyEmbed, virtual: true + end + end + defmodule MySchemaNoPK do use Ecto.Schema From 03c9300e4b2445610a5d091ac6a2af461408607e Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Wed, 21 Jun 2023 15:06:17 +0600 Subject: [PATCH 2/5] Attempt integration test --- integration_test/cases/repo.exs | 9 ++++++++- integration_test/support/schemas.exs | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/integration_test/cases/repo.exs b/integration_test/cases/repo.exs index 5b3e39360f..7c865b2f79 100644 --- a/integration_test/cases/repo.exs +++ b/integration_test/cases/repo.exs @@ -1308,10 +1308,17 @@ defmodule Ecto.Integration.RepoTest do end test "virtual field" do - assert %Post{id: id} = TestRepo.insert!(%Post{title: "1"}) + assert %Post{id: id} = TestRepo.insert!(%Post{title: "1", temp: "special"}) assert TestRepo.get(Post, id).temp == "temp" end + test "virtual embed" do + assert %Order{id: id, last_seen_item: %Item{reference: "1"}} = + TestRepo.insert!(%Order{last_seen_item: %Item{reference: "1"}}) + + assert %{last_seen_item: nil} = TestRepo.get(Order, id) + end + ## Query syntax defmodule Foo do diff --git a/integration_test/support/schemas.exs b/integration_test/support/schemas.exs index 5dbb78f707..ce5aa98058 100644 --- a/integration_test/support/schemas.exs +++ b/integration_test/support/schemas.exs @@ -275,6 +275,7 @@ defmodule Ecto.Integration.Order do * Embedding one schema * Preloading items inside embeds_many * Preloading items inside embeds_one + * Virtual embeds * Field source with json_extract_path """ @@ -282,6 +283,7 @@ defmodule Ecto.Integration.Order do schema "orders" do field :metadata, :map, source: :meta + embeds_one :last_seen_item, Ecto.Integration.Item, virtual: true embeds_one :item, Ecto.Integration.Item embeds_many :items, Ecto.Integration.Item belongs_to :permalink, Ecto.Integration.Permalink From b1bee2af50ecce5fa079f617ecc5707ef075206e Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Wed, 21 Jun 2023 15:16:36 +0600 Subject: [PATCH 3/5] Fix tests --- integration_test/cases/repo.exs | 1 + test/ecto/query/planner_test.exs | 13 ++++++++++++- test/ecto/repo_test.exs | 9 --------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/integration_test/cases/repo.exs b/integration_test/cases/repo.exs index 7c865b2f79..ccfd8bbf5f 100644 --- a/integration_test/cases/repo.exs +++ b/integration_test/cases/repo.exs @@ -6,6 +6,7 @@ defmodule Ecto.Integration.RepoTest do alias Ecto.Integration.Post alias Ecto.Integration.Order + alias Ecto.Integration.Item alias Ecto.Integration.User alias Ecto.Integration.Comment alias Ecto.Integration.Permalink diff --git a/test/ecto/query/planner_test.exs b/test/ecto/query/planner_test.exs index 2eca569430..618e158fe7 100644 --- a/test/ecto/query/planner_test.exs +++ b/test/ecto/query/planner_test.exs @@ -601,7 +601,7 @@ defmodule Ecto.Query.PlannerTest do test "plan: generates a cache key" do {_query, _cast_params, _dump_params, key} = plan(from(Post, [])) - assert key == [:all, {:from, {"posts", Post, 50_009_106, "my_prefix"}, []}] + assert key == [:all, {:from, {"posts", Post, 23210922, "my_prefix"}, []}] query = from( @@ -622,6 +622,7 @@ defmodule Ecto.Query.PlannerTest do ) {_query, _cast_params, _dump_params, key} = plan(%{query | prefix: "foo"}) +<<<<<<< HEAD assert key == [ :all, @@ -636,6 +637,16 @@ defmodule Ecto.Query.PlannerTest do {:from, {"posts", Post, 50_009_106, "hello"}, ["hint"]}, {:select, 1} ] +======= + assert key == [:all, + {:lock, "foo"}, + {:prefix, "foo"}, + {:limit, {true, 1}}, + {:where, [{:and, {:is_nil, [], [nil]}}, {:or, {:is_nil, [], [nil]}}]}, + {:join, [{:inner, {"comments", Comment, 38292156, "world"}, true, ["join hint"]}]}, + {:from, {"posts", Post, 23210922, "hello"}, ["hint"]}, + {:select, 1}] +>>>>>>> e779c5c7 (Fix tests) end test "plan: generates a cache key for in based on the adapter" do diff --git a/test/ecto/repo_test.exs b/test/ecto/repo_test.exs index 5bf658cfc9..82f5bd99d8 100644 --- a/test/ecto/repo_test.exs +++ b/test/ecto/repo_test.exs @@ -160,15 +160,6 @@ defmodule Ecto.RepoTest do end end - defmodule MySchemaVirtualEmbed do - use Ecto.Schema - - schema "my_schema" do - field :x, :string - embeds_one :virtual_embed, MyEmbed, virtual: true - end - end - defmodule MySchemaNoPK do use Ecto.Schema From d355d2c69243448965c8447f04061794d6105a70 Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Tue, 24 Dec 2024 20:44:40 -0300 Subject: [PATCH 4/5] post rebase fixes --- lib/ecto/embedded.ex | 4 ++-- test/ecto/query/planner_test.exs | 27 +++++++++++++-------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/ecto/embedded.ex b/lib/ecto/embedded.ex index 072e790cbf..0aadda57b8 100644 --- a/lib/ecto/embedded.ex +++ b/lib/ecto/embedded.ex @@ -170,8 +170,8 @@ defmodule Ecto.Embedded do {:embed, %{virtual: true}} -> acc - end) - end + end + end) end defp prepare_each(%{cardinality: :one}, nil, _adapter, _repo, _repo_action) do diff --git a/test/ecto/query/planner_test.exs b/test/ecto/query/planner_test.exs index 618e158fe7..10e3f3a696 100644 --- a/test/ecto/query/planner_test.exs +++ b/test/ecto/query/planner_test.exs @@ -601,7 +601,8 @@ defmodule Ecto.Query.PlannerTest do test "plan: generates a cache key" do {_query, _cast_params, _dump_params, key} = plan(from(Post, [])) - assert key == [:all, {:from, {"posts", Post, 23210922, "my_prefix"}, []}] + + assert key == [:all, {:from, {"posts", Post, 132715331, "my_prefix"}, []}] query = from( @@ -622,7 +623,6 @@ defmodule Ecto.Query.PlannerTest do ) {_query, _cast_params, _dump_params, key} = plan(%{query | prefix: "foo"}) -<<<<<<< HEAD assert key == [ :all, @@ -634,19 +634,18 @@ defmodule Ecto.Query.PlannerTest do [ {:inner, {"comments", Comment, 38_292_156, "world"}, true, ["join hint"]} ]}, - {:from, {"posts", Post, 50_009_106, "hello"}, ["hint"]}, + {:from, {"posts", Post, 132715331, "hello"}, ["hint"]}, {:select, 1} ] -======= - assert key == [:all, - {:lock, "foo"}, - {:prefix, "foo"}, - {:limit, {true, 1}}, - {:where, [{:and, {:is_nil, [], [nil]}}, {:or, {:is_nil, [], [nil]}}]}, - {:join, [{:inner, {"comments", Comment, 38292156, "world"}, true, ["join hint"]}]}, - {:from, {"posts", Post, 23210922, "hello"}, ["hint"]}, - {:select, 1}] ->>>>>>> e779c5c7 (Fix tests) + + assert key == [:all, + {:lock, "foo"}, + {:prefix, "foo"}, + {:limit, {true, 1}}, + {:where, [{:and, {:is_nil, [], [nil]}}, {:or, {:is_nil, [], [nil]}}]}, + {:join, [{:inner, {"comments", Comment, 38292156, "world"}, true, ["join hint"]}]}, + {:from, {"posts", Post, 132715331, "hello"}, ["hint"]}, + {:select, 1}] end test "plan: generates a cache key for in based on the adapter" do @@ -966,7 +965,7 @@ defmodule Ecto.Query.PlannerTest do [ :all, {:aliases, %{post: 0}}, - {:from, {"posts", Ecto.Query.PlannerTest.Post, 50_009_106, "my_prefix"}, []}, + {:from, {"posts", Ecto.Query.PlannerTest.Post, 132715331, "my_prefix"}, []}, {:select, {{:%{}, [], [ From 3b695c8fcd7fbdc38fc523b5e1f0aef567b7f078 Mon Sep 17 00:00:00 2001 From: felipe stival <14948182+v0idpwn@users.noreply.github.com> Date: Tue, 24 Dec 2024 20:56:41 -0300 Subject: [PATCH 5/5] Update lib/ecto/embedded.ex --- lib/ecto/embedded.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/ecto/embedded.ex b/lib/ecto/embedded.ex index 0aadda57b8..bd9ac210ea 100644 --- a/lib/ecto/embedded.ex +++ b/lib/ecto/embedded.ex @@ -150,8 +150,6 @@ defmodule Ecto.Embedded do @doc false def prepare(changeset, embeds, adapter, repo_action) do %{changes: changes, types: types, repo: repo} = changeset - #non_virtual_embeds = Enum.reject(embeds, & &1.virtual) - changes |> Map.take(embeds) |> prepare(types, adapter, repo, repo_action)