From 7b0d07e1070608496aa433d1a61dc0067d87acce Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Mon, 13 Dec 2021 16:20:21 +0200 Subject: [PATCH 01/11] log last known call --- lib/ecto/repo.ex | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/ecto/repo.ex b/lib/ecto/repo.ex index 43bdcaf7ff..74ccb538ea 100644 --- a/lib/ecto/repo.ex +++ b/lib/ecto/repo.ex @@ -242,7 +242,7 @@ defmodule Ecto.Repo do adapter.checked_out?(meta) end - @compile {:inline, get_dynamic_repo: 0, with_default_options: 2} + @compile {:inline, get_dynamic_repo: 0, with_default_options: 2, maybe_put_stacktrace: 1} def get_dynamic_repo() do Process.get({__MODULE__, :dynamic_repo}, @default_dynamic_repo) @@ -252,11 +252,32 @@ defmodule Ecto.Repo do Process.put({__MODULE__, :dynamic_repo}, dynamic) || @default_dynamic_repo end - def default_options(_operation), do: [] + # temporary: need to set this in repo options and get it here somehow (?) + def default_options(_operation), do: [get_stacktrace?: true] defoverridable default_options: 1 defp with_default_options(operation_name, opts) do - Keyword.merge(default_options(operation_name), opts) + opts = + operation_name + |> default_options() + |> Keyword.merge(opts) + |> maybe_put_stacktrace() + + opts + end + + defp maybe_put_stacktrace(opts) do + if opts[:get_stacktrace?] do + stacktrace = + self() + |> Process.info(:current_stacktrace) + |> elem(1) + |> List.delete_at(0) + + Keyword.put(opts, :stacktrace, stacktrace) + else + opts + end end ## Transactions From a04b0de384dbf21b4440162f3a0165c8d47e08bc Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Mon, 13 Dec 2021 17:42:06 +0200 Subject: [PATCH 02/11] refactoring --- lib/ecto/repo.ex | 70 +++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/lib/ecto/repo.ex b/lib/ecto/repo.ex index 74ccb538ea..ce73884b92 100644 --- a/lib/ecto/repo.ex +++ b/lib/ecto/repo.ex @@ -200,6 +200,7 @@ defmodule Ecto.Repo do @adapter adapter @default_dynamic_repo opts[:default_dynamic_repo] || __MODULE__ @read_only opts[:read_only] || false + @get_stacktrace? opts[:get_stacktrace?] || false @before_compile adapter @aggregates [:count, :avg, :max, :min, :sum] @@ -242,7 +243,7 @@ defmodule Ecto.Repo do adapter.checked_out?(meta) end - @compile {:inline, get_dynamic_repo: 0, with_default_options: 2, maybe_put_stacktrace: 1} + @compile {:inline, get_dynamic_repo: 0, prepare_options: 2, maybe_put_stacktrace: 1} def get_dynamic_repo() do Process.get({__MODULE__, :dynamic_repo}, @default_dynamic_repo) @@ -253,21 +254,18 @@ defmodule Ecto.Repo do end # temporary: need to set this in repo options and get it here somehow (?) - def default_options(_operation), do: [get_stacktrace?: true] + def default_options(_operation), do: [] defoverridable default_options: 1 - defp with_default_options(operation_name, opts) do - opts = - operation_name - |> default_options() - |> Keyword.merge(opts) - |> maybe_put_stacktrace() - - opts + defp prepare_options(operation_name, opts) do + operation_name + |> default_options() + |> Keyword.merge(opts) + |> maybe_put_stacktrace() end defp maybe_put_stacktrace(opts) do - if opts[:get_stacktrace?] do + if @get_stacktrace? do stacktrace = self() |> Process.info(:current_stacktrace) @@ -284,7 +282,7 @@ defmodule Ecto.Repo do if Ecto.Adapter.Transaction in behaviours do def transaction(fun_or_multi, opts \\ []) do - Ecto.Repo.Transaction.transaction(__MODULE__, get_dynamic_repo(), fun_or_multi, with_default_options(:transaction, opts)) + Ecto.Repo.Transaction.transaction(__MODULE__, get_dynamic_repo(), fun_or_multi, prepare_options(:transaction, opts)) end def in_transaction? do @@ -301,39 +299,39 @@ defmodule Ecto.Repo do if Ecto.Adapter.Schema in behaviours and not @read_only do def insert(struct, opts \\ []) do - Ecto.Repo.Schema.insert(__MODULE__, get_dynamic_repo(), struct, with_default_options(:insert, opts)) + Ecto.Repo.Schema.insert(__MODULE__, get_dynamic_repo(), struct, prepare_options(:insert, opts)) end def update(struct, opts \\ []) do - Ecto.Repo.Schema.update(__MODULE__, get_dynamic_repo(), struct, with_default_options(:update, opts)) + Ecto.Repo.Schema.update(__MODULE__, get_dynamic_repo(), struct, prepare_options(:update, opts)) end def insert_or_update(changeset, opts \\ []) do - Ecto.Repo.Schema.insert_or_update(__MODULE__, get_dynamic_repo(), changeset, with_default_options(:insert_or_update, opts)) + Ecto.Repo.Schema.insert_or_update(__MODULE__, get_dynamic_repo(), changeset, prepare_options(:insert_or_update, opts)) end def delete(struct, opts \\ []) do - Ecto.Repo.Schema.delete(__MODULE__, get_dynamic_repo(), struct, with_default_options(:delete, opts)) + Ecto.Repo.Schema.delete(__MODULE__, get_dynamic_repo(), struct, prepare_options(:delete, opts)) end def insert!(struct, opts \\ []) do - Ecto.Repo.Schema.insert!(__MODULE__, get_dynamic_repo(), struct, with_default_options(:insert, opts)) + Ecto.Repo.Schema.insert!(__MODULE__, get_dynamic_repo(), struct, prepare_options(:insert, opts)) end def update!(struct, opts \\ []) do - Ecto.Repo.Schema.update!(__MODULE__, get_dynamic_repo(), struct, with_default_options(:update, opts)) + Ecto.Repo.Schema.update!(__MODULE__, get_dynamic_repo(), struct, prepare_options(:update, opts)) end def insert_or_update!(changeset, opts \\ []) do - Ecto.Repo.Schema.insert_or_update!(__MODULE__, get_dynamic_repo(), changeset, with_default_options(:insert_or_update, opts)) + Ecto.Repo.Schema.insert_or_update!(__MODULE__, get_dynamic_repo(), changeset, prepare_options(:insert_or_update, opts)) end def delete!(struct, opts \\ []) do - Ecto.Repo.Schema.delete!(__MODULE__, get_dynamic_repo(), struct, with_default_options(:delete, opts)) + Ecto.Repo.Schema.delete!(__MODULE__, get_dynamic_repo(), struct, prepare_options(:delete, opts)) end def insert_all(schema_or_source, entries, opts \\ []) do - Ecto.Repo.Schema.insert_all(__MODULE__, get_dynamic_repo(), schema_or_source, entries, with_default_options(:insert_all, opts)) + Ecto.Repo.Schema.insert_all(__MODULE__, get_dynamic_repo(), schema_or_source, entries, prepare_options(:insert_all, opts)) end end @@ -342,36 +340,36 @@ defmodule Ecto.Repo do if Ecto.Adapter.Queryable in behaviours do if not @read_only do def update_all(queryable, updates, opts \\ []) do - Ecto.Repo.Queryable.update_all(get_dynamic_repo(), queryable, updates, with_default_options(:update_all, opts)) + Ecto.Repo.Queryable.update_all(get_dynamic_repo(), queryable, updates, prepare_options(:update_all, opts)) end def delete_all(queryable, opts \\ []) do - Ecto.Repo.Queryable.delete_all(get_dynamic_repo(), queryable, with_default_options(:delete_all, opts)) + Ecto.Repo.Queryable.delete_all(get_dynamic_repo(), queryable, prepare_options(:delete_all, opts)) end end def all(queryable, opts \\ []) do - Ecto.Repo.Queryable.all(get_dynamic_repo(), queryable, with_default_options(:all, opts)) + Ecto.Repo.Queryable.all(get_dynamic_repo(), queryable, prepare_options(:all, opts)) end def stream(queryable, opts \\ []) do - Ecto.Repo.Queryable.stream(get_dynamic_repo(), queryable, with_default_options(:stream, opts)) + Ecto.Repo.Queryable.stream(get_dynamic_repo(), queryable, prepare_options(:stream, opts)) end def get(queryable, id, opts \\ []) do - Ecto.Repo.Queryable.get(get_dynamic_repo(), queryable, id, with_default_options(:all, opts)) + Ecto.Repo.Queryable.get(get_dynamic_repo(), queryable, id, prepare_options(:all, opts)) end def get!(queryable, id, opts \\ []) do - Ecto.Repo.Queryable.get!(get_dynamic_repo(), queryable, id, with_default_options(:all, opts)) + Ecto.Repo.Queryable.get!(get_dynamic_repo(), queryable, id, prepare_options(:all, opts)) end def get_by(queryable, clauses, opts \\ []) do - Ecto.Repo.Queryable.get_by(get_dynamic_repo(), queryable, clauses, with_default_options(:all, opts)) + Ecto.Repo.Queryable.get_by(get_dynamic_repo(), queryable, clauses, prepare_options(:all, opts)) end def get_by!(queryable, clauses, opts \\ []) do - Ecto.Repo.Queryable.get_by!(get_dynamic_repo(), queryable, clauses, with_default_options(:all, opts)) + Ecto.Repo.Queryable.get_by!(get_dynamic_repo(), queryable, clauses, prepare_options(:all, opts)) end def reload(queryable, opts \\ []) do @@ -383,36 +381,36 @@ defmodule Ecto.Repo do end def one(queryable, opts \\ []) do - Ecto.Repo.Queryable.one(get_dynamic_repo(), queryable, with_default_options(:all, opts)) + Ecto.Repo.Queryable.one(get_dynamic_repo(), queryable, prepare_options(:all, opts)) end def one!(queryable, opts \\ []) do - Ecto.Repo.Queryable.one!(get_dynamic_repo(), queryable, with_default_options(:all, opts)) + Ecto.Repo.Queryable.one!(get_dynamic_repo(), queryable, prepare_options(:all, opts)) end def aggregate(queryable, aggregate, opts \\ []) def aggregate(queryable, aggregate, opts) when aggregate in [:count] and is_list(opts) do - Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, with_default_options(:all, opts)) + Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, prepare_options(:all, opts)) end def aggregate(queryable, aggregate, field) when aggregate in @aggregates and is_atom(field) do - Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, with_default_options(:all, [])) + Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, prepare_options(:all, [])) end def aggregate(queryable, aggregate, field, opts) when aggregate in @aggregates and is_atom(field) and is_list(opts) do - Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, with_default_options(:all, opts)) + Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, prepare_options(:all, opts)) end def exists?(queryable, opts \\ []) do - Ecto.Repo.Queryable.exists?(get_dynamic_repo(), queryable, with_default_options(:all, opts)) + Ecto.Repo.Queryable.exists?(get_dynamic_repo(), queryable, prepare_options(:all, opts)) end def preload(struct_or_structs_or_nil, preloads, opts \\ []) do - Ecto.Repo.Preloader.preload(struct_or_structs_or_nil, get_dynamic_repo(), preloads, with_default_options(:preload, opts)) + Ecto.Repo.Preloader.preload(struct_or_structs_or_nil, get_dynamic_repo(), preloads, prepare_options(:preload, opts)) end def prepare_query(operation, query, opts), do: {query, opts} From 973e4d5e549a2a596c361297b42972096edd426a Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Tue, 28 Dec 2021 10:41:12 +0200 Subject: [PATCH 03/11] wip: refactor to receive option in runtime --- integration_test/cases/repo.exs | 9 +-- lib/ecto/repo.ex | 100 +++++++++++++++++++++----------- lib/ecto/repo/preloader.ex | 75 +++++++++++------------- lib/ecto/repo/queryable.ex | 36 ++++++------ lib/ecto/repo/schema.ex | 98 +++++++++++++++---------------- lib/ecto/repo/transaction.ex | 17 +++--- test/ecto/repo_test.exs | 4 +- 7 files changed, 179 insertions(+), 160 deletions(-) diff --git a/integration_test/cases/repo.exs b/integration_test/cases/repo.exs index 005bbde5c5..12d5fe2066 100644 --- a/integration_test/cases/repo.exs +++ b/integration_test/cases/repo.exs @@ -18,10 +18,11 @@ defmodule Ecto.Integration.RepoTest do assert {:error, {:already_started, _}} = TestRepo.start_link() end - test "supports unnamed repos" do - assert {:ok, pid} = TestRepo.start_link(name: nil) - assert Ecto.Repo.Queryable.all(pid, Post, []) == [] - end + #TODO: I believe this is supposed to be private API + #test "supports unnamed repos" do + # assert {:ok, pid} = TestRepo.start_link(name: nil) + # assert Ecto.Repo.Queryable.all(pid, Post, []) == [] + #end test "all empty" do assert TestRepo.all(Post) == [] diff --git a/lib/ecto/repo.ex b/lib/ecto/repo.ex index ce73884b92..16daea549b 100644 --- a/lib/ecto/repo.ex +++ b/lib/ecto/repo.ex @@ -243,7 +243,7 @@ defmodule Ecto.Repo do adapter.checked_out?(meta) end - @compile {:inline, get_dynamic_repo: 0, prepare_options: 2, maybe_put_stacktrace: 1} + @compile {:inline, get_dynamic_repo: 0, triplet: 3, maybe_put_stacktrace: 2} def get_dynamic_repo() do Process.get({__MODULE__, :dynamic_repo}, @default_dynamic_repo) @@ -257,14 +257,19 @@ defmodule Ecto.Repo do def default_options(_operation), do: [] defoverridable default_options: 1 - defp prepare_options(operation_name, opts) do - operation_name - |> default_options() - |> Keyword.merge(opts) - |> maybe_put_stacktrace() + defp triplet(name, operation_name, opts) do + {adapter, adapter_meta} = Ecto.Repo.Registry.lookup(name) + + opts = + operation_name + |> default_options() + |> Keyword.merge(opts) + |> maybe_put_stacktrace(adapter_meta) + + {adapter, adapter_meta, opts} end - defp maybe_put_stacktrace(opts) do + defp maybe_put_stacktrace(opts, adapter_meta) do if @get_stacktrace? do stacktrace = self() @@ -282,7 +287,8 @@ defmodule Ecto.Repo do if Ecto.Adapter.Transaction in behaviours do def transaction(fun_or_multi, opts \\ []) do - Ecto.Repo.Transaction.transaction(__MODULE__, get_dynamic_repo(), fun_or_multi, prepare_options(:transaction, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Transaction.transaction(__MODULE__, repo, fun_or_multi, triplet(repo, :transaction, opts)) end def in_transaction? do @@ -299,39 +305,48 @@ defmodule Ecto.Repo do if Ecto.Adapter.Schema in behaviours and not @read_only do def insert(struct, opts \\ []) do - Ecto.Repo.Schema.insert(__MODULE__, get_dynamic_repo(), struct, prepare_options(:insert, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Schema.insert(__MODULE__, repo, struct, triplet(repo, :insert, opts)) end def update(struct, opts \\ []) do - Ecto.Repo.Schema.update(__MODULE__, get_dynamic_repo(), struct, prepare_options(:update, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Schema.update(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :update, opts)) end def insert_or_update(changeset, opts \\ []) do - Ecto.Repo.Schema.insert_or_update(__MODULE__, get_dynamic_repo(), changeset, prepare_options(:insert_or_update, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Schema.insert_or_update(__MODULE__, get_dynamic_repo(), changeset, triplet(repo, :insert_or_update, opts)) end def delete(struct, opts \\ []) do - Ecto.Repo.Schema.delete(__MODULE__, get_dynamic_repo(), struct, prepare_options(:delete, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Schema.delete(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :delete, opts)) end def insert!(struct, opts \\ []) do - Ecto.Repo.Schema.insert!(__MODULE__, get_dynamic_repo(), struct, prepare_options(:insert, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Schema.insert!(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :insert, opts)) end def update!(struct, opts \\ []) do - Ecto.Repo.Schema.update!(__MODULE__, get_dynamic_repo(), struct, prepare_options(:update, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Schema.update!(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :update, opts)) end def insert_or_update!(changeset, opts \\ []) do - Ecto.Repo.Schema.insert_or_update!(__MODULE__, get_dynamic_repo(), changeset, prepare_options(:insert_or_update, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Schema.insert_or_update!(__MODULE__, get_dynamic_repo(), changeset, triplet(repo, :insert_or_update, opts)) end def delete!(struct, opts \\ []) do - Ecto.Repo.Schema.delete!(__MODULE__, get_dynamic_repo(), struct, prepare_options(:delete, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Schema.delete!(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :delete, opts)) end def insert_all(schema_or_source, entries, opts \\ []) do - Ecto.Repo.Schema.insert_all(__MODULE__, get_dynamic_repo(), schema_or_source, entries, prepare_options(:insert_all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Schema.insert_all(__MODULE__, get_dynamic_repo(), schema_or_source, entries, triplet(repo, :insert_all, opts)) end end @@ -340,77 +355,94 @@ defmodule Ecto.Repo do if Ecto.Adapter.Queryable in behaviours do if not @read_only do def update_all(queryable, updates, opts \\ []) do - Ecto.Repo.Queryable.update_all(get_dynamic_repo(), queryable, updates, prepare_options(:update_all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.update_all(get_dynamic_repo(), queryable, updates, triplet(repo, :update_all, opts)) end def delete_all(queryable, opts \\ []) do - Ecto.Repo.Queryable.delete_all(get_dynamic_repo(), queryable, prepare_options(:delete_all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.delete_all(get_dynamic_repo(), queryable, triplet(repo, :delete_all, opts)) end end def all(queryable, opts \\ []) do - Ecto.Repo.Queryable.all(get_dynamic_repo(), queryable, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.all(get_dynamic_repo(), queryable, triplet(repo, :all, opts)) end def stream(queryable, opts \\ []) do - Ecto.Repo.Queryable.stream(get_dynamic_repo(), queryable, prepare_options(:stream, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.stream(get_dynamic_repo(), queryable, triplet(repo, :stream, opts)) end def get(queryable, id, opts \\ []) do - Ecto.Repo.Queryable.get(get_dynamic_repo(), queryable, id, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.get(get_dynamic_repo(), queryable, id, triplet(repo, :all, opts)) end def get!(queryable, id, opts \\ []) do - Ecto.Repo.Queryable.get!(get_dynamic_repo(), queryable, id, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.get!(get_dynamic_repo(), queryable, id, triplet(repo, :all, opts)) end def get_by(queryable, clauses, opts \\ []) do - Ecto.Repo.Queryable.get_by(get_dynamic_repo(), queryable, clauses, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.get_by(get_dynamic_repo(), queryable, clauses, triplet(repo, :all, opts)) end def get_by!(queryable, clauses, opts \\ []) do - Ecto.Repo.Queryable.get_by!(get_dynamic_repo(), queryable, clauses, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.get_by!(get_dynamic_repo(), queryable, clauses, triplet(repo, :all, opts)) end def reload(queryable, opts \\ []) do - Ecto.Repo.Queryable.reload(get_dynamic_repo(), queryable, opts) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.reload(get_dynamic_repo(), queryable, triplet(repo, :reload, opts)) end def reload!(queryable, opts \\ []) do - Ecto.Repo.Queryable.reload!(get_dynamic_repo(), queryable, opts) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.reload!(get_dynamic_repo(), queryable, triplet(repo, :reload, opts)) end def one(queryable, opts \\ []) do - Ecto.Repo.Queryable.one(get_dynamic_repo(), queryable, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.one(get_dynamic_repo(), queryable, triplet(repo, :all, opts)) end def one!(queryable, opts \\ []) do - Ecto.Repo.Queryable.one!(get_dynamic_repo(), queryable, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.one!(get_dynamic_repo(), queryable, triplet(repo, :all, opts)) end def aggregate(queryable, aggregate, opts \\ []) def aggregate(queryable, aggregate, opts) when aggregate in [:count] and is_list(opts) do - Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, triplet(repo, :all, opts)) end def aggregate(queryable, aggregate, field) when aggregate in @aggregates and is_atom(field) do - Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, prepare_options(:all, [])) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, triplet(repo, :all, [])) end def aggregate(queryable, aggregate, field, opts) when aggregate in @aggregates and is_atom(field) and is_list(opts) do - Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, triplet(repo, :all, opts)) end def exists?(queryable, opts \\ []) do - Ecto.Repo.Queryable.exists?(get_dynamic_repo(), queryable, prepare_options(:all, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Queryable.exists?(get_dynamic_repo(), queryable, triplet(repo, :all, opts)) end def preload(struct_or_structs_or_nil, preloads, opts \\ []) do - Ecto.Repo.Preloader.preload(struct_or_structs_or_nil, get_dynamic_repo(), preloads, prepare_options(:preload, opts)) + repo = get_dynamic_repo() + Ecto.Repo.Preloader.preload(struct_or_structs_or_nil, get_dynamic_repo(), preloads, triplet(repo, :preload, opts)) end def prepare_query(operation, query, opts), do: {query, opts} diff --git a/lib/ecto/repo/preloader.ex b/lib/ecto/repo/preloader.ex index b9713ae39f..1913f8fcc8 100644 --- a/lib/ecto/repo/preloader.ex +++ b/lib/ecto/repo/preloader.ex @@ -11,13 +11,13 @@ defmodule Ecto.Repo.Preloader do the associations onto their parent schema. """ @spec query([list], Ecto.Repo.t, list, Access.t, fun, Keyword.t) :: [list] - def query([], _repo_name, _preloads, _take, _fun, _opts), do: [] - def query(rows, _repo_name, [], _take, fun, _opts), do: Enum.map(rows, fun) + def query([], _repo_name, _preloads, _take, _fun, _triplet), do: [] + def query(rows, _repo_name, [], _take, fun, _triplet), do: Enum.map(rows, fun) - def query(rows, repo_name, preloads, take, fun, opts) do + def query(rows, repo_name, preloads, take, fun, triplet) do rows |> extract() - |> normalize_and_preload_each(repo_name, preloads, take, opts) + |> normalize_and_preload_each(repo_name, preloads, take, triplet) |> unextract(rows, fun) end @@ -34,21 +34,21 @@ defmodule Ecto.Repo.Preloader do """ @spec preload(structs, atom, atom | list, Keyword.t) :: structs when structs: [Ecto.Schema.t] | Ecto.Schema.t | nil - def preload(nil, _repo_name, _preloads, _opts) do + def preload(nil, _repo_name, _preloads, _triplet) do nil end - def preload(structs, repo_name, preloads, opts) when is_list(structs) do - normalize_and_preload_each(structs, repo_name, preloads, opts[:take], opts) + def preload(structs, repo_name, preloads, {_adapter, _adapter_meta, opts} = triplet) when is_list(structs) do + normalize_and_preload_each(structs, repo_name, preloads, opts[:take], triplet) end - def preload(struct, repo_name, preloads, opts) when is_map(struct) do - normalize_and_preload_each([struct], repo_name, preloads, opts[:take], opts) |> hd() + def preload(struct, repo_name, preloads, {_adapter, _adapter_meta, opts} = triplet) when is_map(struct) do + normalize_and_preload_each([struct], repo_name, preloads, opts[:take], triplet) |> hd() end - defp normalize_and_preload_each(structs, repo_name, preloads, take, opts) do + defp normalize_and_preload_each(structs, repo_name, preloads, take, triplet) do preloads = normalize(preloads, take, preloads) - preload_each(structs, repo_name, preloads, opts) + preload_each(structs, repo_name, preloads, triplet) rescue e -> # Reraise errors so we ignore the preload inner stacktrace @@ -57,19 +57,19 @@ defmodule Ecto.Repo.Preloader do ## Preloading - defp preload_each(structs, _repo_name, [], _opts), do: structs - defp preload_each([], _repo_name, _preloads, _opts), do: [] - defp preload_each(structs, repo_name, preloads, opts) do + defp preload_each(structs, _repo_name, [], _triplet), do: structs + defp preload_each([], _repo_name, _preloads, _triplet), do: [] + defp preload_each(structs, repo_name, preloads, triplet) do if sample = Enum.find(structs, & &1) do module = sample.__struct__ - prefix = preload_prefix(opts, sample) + prefix = preload_prefix(triplet, sample) {assocs, throughs} = expand(module, preloads, {%{}, %{}}) {fetched_assocs, to_fetch_queries} = - prepare_queries(structs, module, assocs, prefix, repo_name, opts) + prepare_queries(structs, module, assocs, prefix, repo_name, triplet) - fetched_queries = maybe_pmap(to_fetch_queries, repo_name, opts) - assocs = preload_assocs(fetched_assocs, fetched_queries, repo_name, opts) + fetched_queries = maybe_pmap(to_fetch_queries, repo_name, triplet) + assocs = preload_assocs(fetched_assocs, fetched_queries, repo_name, triplet) throughs = Map.values(throughs) for struct <- structs do @@ -82,7 +82,7 @@ defmodule Ecto.Repo.Preloader do end end - defp preload_prefix(opts, sample) do + defp preload_prefix({_adapter, _adapter_meta, opts}, sample) do case Keyword.fetch(opts, :prefix) do {:ok, prefix} -> prefix @@ -98,16 +98,16 @@ defmodule Ecto.Repo.Preloader do ## Association preloading # First we traverse all assocs and find which queries we need to run. - defp prepare_queries(structs, module, assocs, prefix, repo_name, opts) do + defp prepare_queries(structs, module, assocs, prefix, repo_name, triplet) do Enum.reduce(assocs, {[], []}, fn {_key, {{:assoc, assoc, related_key}, take, query, preloads}}, {assocs, queries} -> - {fetch_ids, loaded_ids, loaded_structs} = fetch_ids(structs, module, assoc, opts) + {fetch_ids, loaded_ids, loaded_structs} = fetch_ids(structs, module, assoc, triplet) queries = if fetch_ids != [] do [ - fn opts -> - fetch_query(fetch_ids, assoc, repo_name, query, prefix, related_key, take, opts) + fn triplet -> + fetch_query(fetch_ids, assoc, repo_name, query, prefix, related_key, take, triplet) end | queries ] @@ -120,8 +120,8 @@ defmodule Ecto.Repo.Preloader do end # Then we execute queries in parallel - defp maybe_pmap(preloaders, repo_name, opts) do - if match?([_,_|_], preloaders) and not checked_out?(repo_name) and + defp maybe_pmap(preloaders, _repo_name, {adapter, adapter_meta, opts}) do + if match?([_,_|_], preloaders) and not adapter.checked_out?(adapter_meta) and Keyword.get(opts, :in_parallel, true) do # We pass caller: self() so the ownership pool knows where # to fetch the connection from and set the proper timeouts. @@ -131,37 +131,32 @@ defmodule Ecto.Repo.Preloader do opts = Keyword.put_new(opts, :caller, self()) preloaders - |> Task.async_stream(&(&1.(opts)), timeout: :infinity) + |> Task.async_stream(&(&1.({adapter, adapter_meta, opts})), timeout: :infinity) |> Enum.map(fn {:ok, assoc} -> assoc end) else - Enum.map(preloaders, &(&1.(opts))) + Enum.map(preloaders, &(&1.({adapter, adapter_meta, opts}))) end end - defp checked_out?(repo_name) do - {adapter, meta} = Ecto.Repo.Registry.lookup(repo_name) - adapter.checked_out?(meta) - end - # Then we unpack the query results, merge them, and preload recursively defp preload_assocs( [{assoc, query?, loaded_ids, loaded_structs, preloads} | assocs], queries, repo_name, - opts + triplet ) do {fetch_ids, fetch_structs, queries} = maybe_unpack_query(query?, queries) - all = preload_each(Enum.reverse(loaded_structs, fetch_structs), repo_name, preloads, opts) + all = preload_each(Enum.reverse(loaded_structs, fetch_structs), repo_name, preloads, triplet) entry = {:assoc, assoc, assoc_map(assoc.cardinality, Enum.reverse(loaded_ids, fetch_ids), all)} - [entry | preload_assocs(assocs, queries, repo_name, opts)] + [entry | preload_assocs(assocs, queries, repo_name, triplet)] end - defp preload_assocs([], [], _repo_name, _opts), do: [] + defp preload_assocs([], [], _repo_name, _triplet), do: [] defp maybe_unpack_query(false, queries), do: {[], [], queries} defp maybe_unpack_query(true, [{ids, structs} | queries]), do: {ids, structs, queries} - defp fetch_ids(structs, module, assoc, opts) do + defp fetch_ids(structs, module, assoc, {_adapter, _adapter_meta, opts}) do %{field: field, owner_key: owner_key, cardinality: card} = assoc force? = Keyword.get(opts, :force, false) @@ -198,7 +193,7 @@ defmodule Ecto.Repo.Preloader do end end - defp fetch_query(ids, assoc, _repo_name, query, _prefix, related_key, _take, _opts) when is_function(query, 1) do + defp fetch_query(ids, assoc, _repo_name, query, _prefix, related_key, _take, _triplet) when is_function(query, 1) do # Note we use an explicit sort because we don't want # to reorder based on the struct. Only the ID. ids @@ -209,7 +204,7 @@ defmodule Ecto.Repo.Preloader do |> unzip_ids([], []) end - defp fetch_query(ids, %{cardinality: card} = assoc, repo_name, query, prefix, related_key, take, opts) do + defp fetch_query(ids, %{cardinality: card} = assoc, repo_name, query, prefix, related_key, take, triplet) do query = assoc.__struct__.assoc_query(assoc, query, Enum.uniq(ids)) field = related_key_to_field(query, related_key) @@ -231,7 +226,7 @@ defmodule Ecto.Repo.Preloader do query end - unzip_ids Ecto.Repo.Queryable.all(repo_name, query, opts), [], [] + unzip_ids Ecto.Repo.Queryable.all(repo_name, query, triplet), [], [] end defp fetched_records_to_tuple_ids([], _assoc, _related_key), diff --git a/lib/ecto/repo/queryable.ex b/lib/ecto/repo/queryable.ex index f1014bce9e..cb0d1121d8 100644 --- a/lib/ecto/repo/queryable.ex +++ b/lib/ecto/repo/queryable.ex @@ -10,22 +10,21 @@ defmodule Ecto.Repo.Queryable do require Ecto.Query - def all(name, queryable, opts) when is_list(opts) do + def all(name, queryable, triplet) do query = queryable |> Ecto.Queryable.to_query() |> Ecto.Query.Planner.ensure_select(true) - execute(:all, name, query, opts) |> elem(1) + execute(:all, name, query, triplet) |> elem(1) end - def stream(name, queryable, opts) when is_list(opts) do + def stream(_name, queryable, {adapter, %{cache: cache, repo: repo} = adapter_meta, opts}) do query = queryable |> Ecto.Queryable.to_query() |> Ecto.Query.Planner.ensure_select(true) - {adapter, %{cache: cache, repo: repo} = adapter_meta} = Ecto.Repo.Registry.lookup(name) {query, opts} = repo.prepare_query(:stream, query, opts) query = attach_prefix(query, opts) {query_meta, prepared, params} = Planner.query(query, :all, cache, adapter, 0) @@ -142,39 +141,39 @@ defmodule Ecto.Repo.Queryable do %{query | combinations: combinations} end - def one(name, queryable, opts) do - case all(name, queryable, opts) do + def one(name, queryable, triplet) do + case all(name, queryable, triplet) do [one] -> one [] -> nil other -> raise Ecto.MultipleResultsError, queryable: queryable, count: length(other) end end - def one!(name, queryable, opts) do - case all(name, queryable, opts) do + def one!(name, queryable, triplet) do + case all(name, queryable, triplet) do [one] -> one [] -> raise Ecto.NoResultsError, queryable: queryable other -> raise Ecto.MultipleResultsError, queryable: queryable, count: length(other) end end - def update_all(name, queryable, [], opts) when is_list(opts) do - update_all(name, queryable, opts) + def update_all(name, queryable, [], triplet) do + update_all(name, queryable, triplet) end - def update_all(name, queryable, updates, opts) when is_list(opts) do + def update_all(name, queryable, updates, triplet) do query = Query.from(queryable, update: ^updates) - update_all(name, query, opts) + update_all(name, query, triplet) end - defp update_all(name, queryable, opts) do + defp update_all(name, queryable, triplet) do query = Ecto.Queryable.to_query(queryable) - execute(:update_all, name, query, opts) + execute(:update_all, name, query, triplet) end - def delete_all(name, queryable, opts) when is_list(opts) do + def delete_all(name, queryable, triplet) do query = Ecto.Queryable.to_query(queryable) - execute(:delete_all, name, query, opts) + execute(:delete_all, name, query, triplet) end @doc """ @@ -196,8 +195,7 @@ defmodule Ecto.Repo.Queryable do ## Helpers - defp execute(operation, name, query, opts) when is_list(opts) do - {adapter, %{cache: cache, repo: repo} = adapter_meta} = Ecto.Repo.Registry.lookup(name) + defp execute(operation, name, query, {adapter, %{cache: cache, repo: repo} = adapter_meta, opts} = triplet) do {query, opts} = repo.prepare_query(operation, query, opts) query = attach_prefix(query, opts) {query_meta, prepared, params} = Planner.query(query, operation, cache, adapter, 0) @@ -222,7 +220,7 @@ defmodule Ecto.Repo.Queryable do {count, rows |> Ecto.Repo.Assoc.query(assocs, sources, preprocessor) - |> Ecto.Repo.Preloader.query(name, preloads, take, postprocessor, opts)} + |> Ecto.Repo.Preloader.query(name, preloads, take, postprocessor, triplet)} end end diff --git a/lib/ecto/repo/schema.ex b/lib/ecto/repo/schema.ex index 248e4a7b2a..6156bf44c4 100644 --- a/lib/ecto/repo/schema.ex +++ b/lib/ecto/repo/schema.ex @@ -12,20 +12,20 @@ defmodule Ecto.Repo.Schema do @doc """ Implementation for `Ecto.Repo.insert_all/3`. """ - def insert_all(repo, name, schema, rows, opts) when is_atom(schema) do + def insert_all(repo, name, schema, rows, triplet) when is_atom(schema) do do_insert_all(repo, name, schema, schema.__schema__(:prefix), - schema.__schema__(:source), rows, opts) + schema.__schema__(:source), rows, triplet) end - def insert_all(repo, name, table, rows, opts) when is_binary(table) do - do_insert_all(repo, name, nil, nil, table, rows, opts) + def insert_all(repo, name, table, rows, triplet) when is_binary(table) do + do_insert_all(repo, name, nil, nil, table, rows, triplet) end - def insert_all(repo, name, {source, schema}, rows, opts) when is_atom(schema) do - do_insert_all(repo, name, schema, schema.__schema__(:prefix), source, rows, opts) + def insert_all(repo, name, {source, schema}, rows, triplet) when is_atom(schema) do + do_insert_all(repo, name, schema, schema.__schema__(:prefix), source, rows, triplet) end - defp do_insert_all(_repo, _name, _schema, _prefix, _source, [], opts) do + defp do_insert_all(_repo, _name, _schema, _prefix, _source, [], {_adapter, _adapter_meta, opts}) do if opts[:returning] do {0, []} else @@ -33,8 +33,7 @@ defmodule Ecto.Repo.Schema do end end - defp do_insert_all(repo, name, schema, prefix, source, rows_or_query, opts) do - {adapter, adapter_meta} = Ecto.Repo.Registry.lookup(name) + defp do_insert_all(repo, _name, schema, prefix, source, rows_or_query, {adapter, adapter_meta, opts}) do autogen_id = schema && schema.__schema__(:autogenerate_id) dumper = schema && schema.__schema__(:dump) placeholder_map = Keyword.get(opts, :placeholders, %{}) @@ -265,8 +264,8 @@ defmodule Ecto.Repo.Schema do @doc """ Implementation for `Ecto.Repo.insert!/2`. """ - def insert!(repo, name, struct_or_changeset, opts) do - case insert(repo, name, struct_or_changeset, opts) do + def insert!(repo, name, struct_or_changeset, triplet) do + case insert(repo, name, struct_or_changeset, triplet) do {:ok, struct} -> struct @@ -278,8 +277,8 @@ defmodule Ecto.Repo.Schema do @doc """ Implementation for `Ecto.Repo.update!/2`. """ - def update!(repo, name, struct_or_changeset, opts) do - case update(repo, name, struct_or_changeset, opts) do + def update!(repo, name, struct_or_changeset, triplet) do + case update(repo, name, struct_or_changeset, triplet) do {:ok, struct} -> struct @@ -291,8 +290,8 @@ defmodule Ecto.Repo.Schema do @doc """ Implementation for `Ecto.Repo.delete!/2`. """ - def delete!(repo, name, struct_or_changeset, opts) do - case delete(repo, name, struct_or_changeset, opts) do + def delete!(repo, name, struct_or_changeset, triplet) do + case delete(repo, name, struct_or_changeset, triplet) do {:ok, struct} -> struct @@ -304,16 +303,15 @@ defmodule Ecto.Repo.Schema do @doc """ Implementation for `Ecto.Repo.insert/2`. """ - def insert(repo, name, %Changeset{} = changeset, opts) when is_list(opts) do - do_insert(repo, name, changeset, opts) + def insert(repo, name, %Changeset{} = changeset, triplet) do + do_insert(repo, name, changeset, triplet) end - def insert(repo, name, %{__struct__: _} = struct, opts) when is_list(opts) do - do_insert(repo, name, Ecto.Changeset.change(struct), opts) + def insert(repo, name, %{__struct__: _} = struct, triplet) do + do_insert(repo, name, Ecto.Changeset.change(struct), triplet) end - defp do_insert(repo, name, %Changeset{valid?: true} = changeset, opts) do - {adapter, adapter_meta} = Ecto.Repo.Registry.lookup(name) + defp do_insert(repo, _name, %Changeset{valid?: true} = changeset, {adapter, adapter_meta, opts} = triplet) do %{prepare: prepare, repo_opts: repo_opts} = changeset opts = Keyword.merge(repo_opts, opts) @@ -336,7 +334,7 @@ defmodule Ecto.Repo.Schema do # On insert, we always merge the whole struct into the # changeset as changes, except the primary key if it is nil. - changeset = put_repo_and_action(changeset, :insert, repo, opts) + changeset = put_repo_and_action(changeset, :insert, repo, triplet) changeset = Relation.surface_changes(changeset, struct, fields ++ assocs) wrap_in_transaction(adapter, adapter_meta, opts, changeset, assocs, embeds, prepare, fn -> @@ -381,25 +379,24 @@ defmodule Ecto.Repo.Schema do end) end - defp do_insert(repo, _name, %Changeset{valid?: false} = changeset, opts) do - {:error, put_repo_and_action(changeset, :insert, repo, opts)} + defp do_insert(repo, _name, %Changeset{valid?: false} = changeset, triplet) do + {:error, put_repo_and_action(changeset, :insert, repo, triplet)} end @doc """ Implementation for `Ecto.Repo.update/2`. """ - def update(repo, name, %Changeset{} = changeset, opts) when is_list(opts) do - do_update(repo, name, changeset, opts) + def update(repo, name, %Changeset{} = changeset, triplet) do + do_update(repo, name, changeset, triplet) end - def update(_repo, _name, %{__struct__: _}, opts) when is_list(opts) do + def update(_repo, _name, %{__struct__: _}, _triplet) do raise ArgumentError, "giving a struct to Ecto.Repo.update/2 is not supported. " <> "Ecto is unable to properly track changes when a struct is given, " <> "an Ecto.Changeset must be given instead" end - defp do_update(repo, name, %Changeset{valid?: true} = changeset, opts) do - {adapter, adapter_meta} = Ecto.Repo.Registry.lookup(name) + defp do_update(repo, _name, %Changeset{valid?: true} = changeset, {adapter, adapter_meta, opts} = triplet) do %{prepare: prepare, repo_opts: repo_opts} = changeset opts = Keyword.merge(repo_opts, opts) @@ -422,7 +419,7 @@ defmodule Ecto.Repo.Schema do # Differently from insert, update does not copy the struct # fields into the changeset. All changes must be in the # changeset before hand. - changeset = put_repo_and_action(changeset, :update, repo, opts) + changeset = put_repo_and_action(changeset, :update, repo, triplet) if changeset.changes != %{} or force? do wrap_in_transaction(adapter, adapter_meta, opts, changeset, assocs, embeds, prepare, fn -> @@ -466,17 +463,17 @@ defmodule Ecto.Repo.Schema do end end - defp do_update(repo, _name, %Changeset{valid?: false} = changeset, opts) do - {:error, put_repo_and_action(changeset, :update, repo, opts)} + defp do_update(repo, _name, %Changeset{valid?: false} = changeset, triplet) do + {:error, put_repo_and_action(changeset, :update, repo, triplet)} end @doc """ Implementation for `Ecto.Repo.insert_or_update/2`. """ - def insert_or_update(repo, name, changeset, opts) do + def insert_or_update(repo, name, changeset, triplet) do case get_state(changeset) do - :built -> insert(repo, name, changeset, opts) - :loaded -> update(repo, name, changeset, opts) + :built -> insert(repo, name, changeset, triplet) + :loaded -> update(repo, name, changeset, triplet) state -> raise ArgumentError, "the changeset has an invalid state " <> "for Repo.insert_or_update/2: #{state}" end @@ -485,10 +482,10 @@ defmodule Ecto.Repo.Schema do @doc """ Implementation for `Ecto.Repo.insert_or_update!/2`. """ - def insert_or_update!(repo, name, changeset, opts) do + def insert_or_update!(repo, name, changeset, triplet) do case get_state(changeset) do - :built -> insert!(repo, name, changeset, opts) - :loaded -> update!(repo, name, changeset, opts) + :built -> insert!(repo, name, changeset, triplet) + :loaded -> update!(repo, name, changeset, triplet) state -> raise ArgumentError, "the changeset has an invalid state " <> "for Repo.insert_or_update!/2: #{state}" end @@ -504,17 +501,16 @@ defmodule Ecto.Repo.Schema do @doc """ Implementation for `Ecto.Repo.delete/2`. """ - def delete(repo, name, %Changeset{} = changeset, opts) when is_list(opts) do - do_delete(repo, name, changeset, opts) + def delete(repo, name, %Changeset{} = changeset, triplet) do + do_delete(repo, name, changeset, triplet) end - def delete(repo, name, %{__struct__: _} = struct, opts) when is_list(opts) do + def delete(repo, name, %{__struct__: _} = struct, triplet) do changeset = Ecto.Changeset.change(struct) - do_delete(repo, name, changeset, opts) + do_delete(repo, name, changeset, triplet) end - defp do_delete(repo, name, %Changeset{valid?: true} = changeset, opts) do - {adapter, adapter_meta} = Ecto.Repo.Registry.lookup(name) + defp do_delete(repo, name, %Changeset{valid?: true} = changeset, {adapter, adapter_meta, opts} = triplet) do %{prepare: prepare, repo_opts: repo_opts} = changeset opts = Keyword.merge(repo_opts, opts) @@ -522,7 +518,7 @@ defmodule Ecto.Repo.Schema do schema = struct.__struct__ assocs = to_delete_assocs(schema) dumper = schema.__schema__(:dump) - changeset = put_repo_and_action(changeset, :delete, repo, opts) + changeset = put_repo_and_action(changeset, :delete, repo, triplet) wrap_in_transaction(adapter, adapter_meta, opts, assocs != [], prepare, fn -> changeset = run_prepare(changeset, prepare) @@ -533,7 +529,7 @@ defmodule Ecto.Repo.Schema do # Delete related associations for %{__struct__: mod, on_delete: on_delete} = reflection <- assocs do - apply(mod, on_delete, [reflection, changeset.data, name, opts]) + apply(mod, on_delete, [reflection, changeset.data, name, triplet]) end schema_meta = metadata(struct, schema.__schema__(:autogenerate_id), opts) @@ -553,8 +549,8 @@ defmodule Ecto.Repo.Schema do end) end - defp do_delete(repo, _name, %Changeset{valid?: false} = changeset, opts) do - {:error, put_repo_and_action(changeset, :delete, repo, opts)} + defp do_delete(repo, _name, %Changeset{valid?: false} = changeset, triplet) do + {:error, put_repo_and_action(changeset, :delete, repo, triplet)} end def load(adapter, schema_or_types, data) do @@ -608,7 +604,7 @@ defmodule Ecto.Repo.Schema do defp struct_from_changeset!(_action, %{data: struct}), do: struct - defp put_repo_and_action(%{action: :ignore, valid?: valid?} = changeset, action, repo, opts) do + defp put_repo_and_action(%{action: :ignore, valid?: valid?} = changeset, action, repo, {_adapter, _adapter_meta, opts}) do if valid? do raise ArgumentError, "a valid changeset with action :ignore was given to " <> "#{inspect repo}.#{action}/2. Changesets can only be ignored " <> @@ -617,9 +613,9 @@ defmodule Ecto.Repo.Schema do %{changeset | action: action, repo: repo, repo_opts: opts} end end - defp put_repo_and_action(%{action: given}, action, repo, _opts) when given != nil and given != action, + defp put_repo_and_action(%{action: given}, action, repo, _triplet) when given != nil and given != action, do: raise ArgumentError, "a changeset with action #{inspect given} was given to #{inspect repo}.#{action}/2" - defp put_repo_and_action(changeset, action, repo, opts), + defp put_repo_and_action(changeset, action, repo, {_adapter, _adapter_meta, opts}), do: %{changeset | action: action, repo: repo, repo_opts: opts} defp run_prepare(changeset, prepare) do diff --git a/lib/ecto/repo/transaction.ex b/lib/ecto/repo/transaction.ex index d92ab86147..3a1fa7d82d 100644 --- a/lib/ecto/repo/transaction.ex +++ b/lib/ecto/repo/transaction.ex @@ -2,20 +2,17 @@ defmodule Ecto.Repo.Transaction do @moduledoc false @dialyzer :no_opaque - def transaction(_repo, name, fun, opts) when is_function(fun, 0) do - {adapter, meta} = Ecto.Repo.Registry.lookup(name) - adapter.transaction(meta, opts, fun) + def transaction(_repo, _name, fun, {adapter, adapter_meta, opts}) when is_function(fun, 0) do + adapter.transaction(adapter_meta, opts, fun) end - def transaction(repo, name, fun, opts) when is_function(fun, 1) do - {adapter, meta} = Ecto.Repo.Registry.lookup(name) - adapter.transaction(meta, opts, fn -> fun.(repo) end) + def transaction(repo, _name, fun, {adapter, adapter_meta, opts}) when is_function(fun, 1) do + adapter.transaction(adapter_meta, opts, fn -> fun.(repo) end) end - def transaction(repo, name, %Ecto.Multi{} = multi, opts) do - {adapter, meta} = Ecto.Repo.Registry.lookup(name) - wrap = &adapter.transaction(meta, opts, &1) - return = &adapter.rollback(meta, &1) + def transaction(repo, _name, %Ecto.Multi{} = multi, {adapter, adapter_meta, opts}) do + wrap = &adapter.transaction(adapter_meta, opts, &1) + return = &adapter.rollback(adapter_meta, &1) case Ecto.Multi.__apply__(multi, repo, wrap, return) do {:ok, values} -> {:ok, values} diff --git a/test/ecto/repo_test.exs b/test/ecto/repo_test.exs index 8bbd69ad4b..ad16adff0f 100644 --- a/test/ecto/repo_test.exs +++ b/test/ecto/repo_test.exs @@ -1661,13 +1661,13 @@ defmodule Ecto.RepoTest do test "stream" do query = from p in MyParent, select: p PrepareRepo.stream(query, [hello: :world]) |> Enum.to_list() - assert_received {:stream, ^query, [hello: :world]} + assert_received {:stream, ^query, _} assert_received {:stream, %{prefix: "rewritten"}} end test "preload" do PrepareRepo.preload(%MySchemaWithAssoc{parent_id: 1}, :parent, [hello: :world]) - assert_received {:all, query, [hello: :world]} + assert_received {:all, query, _} assert query.from.source == {"my_parent", Ecto.RepoTest.MyParent} end end From c52f038bfea2c84cec8d4029bec39fb4d451781d Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Tue, 28 Dec 2021 12:06:48 +0200 Subject: [PATCH 04/11] read option in runtime --- lib/ecto/repo.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ecto/repo.ex b/lib/ecto/repo.ex index 16daea549b..b07fb2df77 100644 --- a/lib/ecto/repo.ex +++ b/lib/ecto/repo.ex @@ -200,7 +200,6 @@ defmodule Ecto.Repo do @adapter adapter @default_dynamic_repo opts[:default_dynamic_repo] || __MODULE__ @read_only opts[:read_only] || false - @get_stacktrace? opts[:get_stacktrace?] || false @before_compile adapter @aggregates [:count, :avg, :max, :min, :sum] @@ -257,6 +256,7 @@ defmodule Ecto.Repo do def default_options(_operation), do: [] defoverridable default_options: 1 + # TODO: Make this function public? defp triplet(name, operation_name, opts) do {adapter, adapter_meta} = Ecto.Repo.Registry.lookup(name) @@ -270,7 +270,7 @@ defmodule Ecto.Repo do end defp maybe_put_stacktrace(opts, adapter_meta) do - if @get_stacktrace? do + if opts[:get_stacktrace?] || adapter_meta[:get_stacktrace?] do stacktrace = self() |> Process.info(:current_stacktrace) From 839cca19de3511cde9ace95e544d925a0ea86d69 Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Tue, 28 Dec 2021 12:31:07 +0200 Subject: [PATCH 05/11] make triplet function public --- lib/ecto/repo.ex | 85 ++++++++++++++----------------------- lib/ecto/repo/supervisor.ex | 23 ++++++++++ 2 files changed, 55 insertions(+), 53 deletions(-) diff --git a/lib/ecto/repo.ex b/lib/ecto/repo.ex index b07fb2df77..8b968f585f 100644 --- a/lib/ecto/repo.ex +++ b/lib/ecto/repo.ex @@ -242,7 +242,7 @@ defmodule Ecto.Repo do adapter.checked_out?(meta) end - @compile {:inline, get_dynamic_repo: 0, triplet: 3, maybe_put_stacktrace: 2} + @compile {:inline, get_dynamic_repo: 0, prepare_opts: 2} def get_dynamic_repo() do Process.get({__MODULE__, :dynamic_repo}, @default_dynamic_repo) @@ -256,31 +256,10 @@ defmodule Ecto.Repo do def default_options(_operation), do: [] defoverridable default_options: 1 - # TODO: Make this function public? - defp triplet(name, operation_name, opts) do - {adapter, adapter_meta} = Ecto.Repo.Registry.lookup(name) - - opts = - operation_name - |> default_options() - |> Keyword.merge(opts) - |> maybe_put_stacktrace(adapter_meta) - - {adapter, adapter_meta, opts} - end - - defp maybe_put_stacktrace(opts, adapter_meta) do - if opts[:get_stacktrace?] || adapter_meta[:get_stacktrace?] do - stacktrace = - self() - |> Process.info(:current_stacktrace) - |> elem(1) - |> List.delete_at(0) - - Keyword.put(opts, :stacktrace, stacktrace) - else - opts - end + defp prepare_opts(operation_name, opts) do + operation_name + |> default_options() + |> Keyword.merge(opts) end ## Transactions @@ -288,7 +267,7 @@ defmodule Ecto.Repo do if Ecto.Adapter.Transaction in behaviours do def transaction(fun_or_multi, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Transaction.transaction(__MODULE__, repo, fun_or_multi, triplet(repo, :transaction, opts)) + Ecto.Repo.Transaction.transaction(__MODULE__, repo, fun_or_multi, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:transaction, opts))) end def in_transaction? do @@ -306,47 +285,47 @@ defmodule Ecto.Repo do if Ecto.Adapter.Schema in behaviours and not @read_only do def insert(struct, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Schema.insert(__MODULE__, repo, struct, triplet(repo, :insert, opts)) + Ecto.Repo.Schema.insert(__MODULE__, repo, struct, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:insert, opts))) end def update(struct, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Schema.update(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :update, opts)) + Ecto.Repo.Schema.update(__MODULE__, get_dynamic_repo(), struct, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:update, opts))) end def insert_or_update(changeset, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Schema.insert_or_update(__MODULE__, get_dynamic_repo(), changeset, triplet(repo, :insert_or_update, opts)) + Ecto.Repo.Schema.insert_or_update(__MODULE__, get_dynamic_repo(), changeset, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:insert_or_update, opts))) end def delete(struct, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Schema.delete(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :delete, opts)) + Ecto.Repo.Schema.delete(__MODULE__, get_dynamic_repo(), struct, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:delete, opts))) end def insert!(struct, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Schema.insert!(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :insert, opts)) + Ecto.Repo.Schema.insert!(__MODULE__, get_dynamic_repo(), struct, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:insert, opts))) end def update!(struct, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Schema.update!(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :update, opts)) + Ecto.Repo.Schema.update!(__MODULE__, get_dynamic_repo(), struct, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:update, opts))) end def insert_or_update!(changeset, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Schema.insert_or_update!(__MODULE__, get_dynamic_repo(), changeset, triplet(repo, :insert_or_update, opts)) + Ecto.Repo.Schema.insert_or_update!(__MODULE__, get_dynamic_repo(), changeset, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:insert_or_update, opts))) end def delete!(struct, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Schema.delete!(__MODULE__, get_dynamic_repo(), struct, triplet(repo, :delete, opts)) + Ecto.Repo.Schema.delete!(__MODULE__, get_dynamic_repo(), struct, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:delete, opts))) end def insert_all(schema_or_source, entries, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Schema.insert_all(__MODULE__, get_dynamic_repo(), schema_or_source, entries, triplet(repo, :insert_all, opts)) + Ecto.Repo.Schema.insert_all(__MODULE__, get_dynamic_repo(), schema_or_source, entries, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:insert_all, opts))) end end @@ -356,63 +335,63 @@ defmodule Ecto.Repo do if not @read_only do def update_all(queryable, updates, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.update_all(get_dynamic_repo(), queryable, updates, triplet(repo, :update_all, opts)) + Ecto.Repo.Queryable.update_all(get_dynamic_repo(), queryable, updates, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:update_all, opts))) end def delete_all(queryable, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.delete_all(get_dynamic_repo(), queryable, triplet(repo, :delete_all, opts)) + Ecto.Repo.Queryable.delete_all(get_dynamic_repo(), queryable, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:delete_all, opts))) end end def all(queryable, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.all(get_dynamic_repo(), queryable, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.all(get_dynamic_repo(), queryable, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def stream(queryable, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.stream(get_dynamic_repo(), queryable, triplet(repo, :stream, opts)) + Ecto.Repo.Queryable.stream(get_dynamic_repo(), queryable, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:stream, opts))) end def get(queryable, id, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.get(get_dynamic_repo(), queryable, id, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.get(get_dynamic_repo(), queryable, id, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def get!(queryable, id, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.get!(get_dynamic_repo(), queryable, id, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.get!(get_dynamic_repo(), queryable, id, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def get_by(queryable, clauses, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.get_by(get_dynamic_repo(), queryable, clauses, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.get_by(get_dynamic_repo(), queryable, clauses, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def get_by!(queryable, clauses, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.get_by!(get_dynamic_repo(), queryable, clauses, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.get_by!(get_dynamic_repo(), queryable, clauses, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def reload(queryable, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.reload(get_dynamic_repo(), queryable, triplet(repo, :reload, opts)) + Ecto.Repo.Queryable.reload(get_dynamic_repo(), queryable, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:reload, opts))) end def reload!(queryable, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.reload!(get_dynamic_repo(), queryable, triplet(repo, :reload, opts)) + Ecto.Repo.Queryable.reload!(get_dynamic_repo(), queryable, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:reload, opts))) end def one(queryable, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.one(get_dynamic_repo(), queryable, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.one(get_dynamic_repo(), queryable, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def one!(queryable, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.one!(get_dynamic_repo(), queryable, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.one!(get_dynamic_repo(), queryable, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def aggregate(queryable, aggregate, opts \\ []) @@ -420,29 +399,29 @@ defmodule Ecto.Repo do def aggregate(queryable, aggregate, opts) when aggregate in [:count] and is_list(opts) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def aggregate(queryable, aggregate, field) when aggregate in @aggregates and is_atom(field) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, triplet(repo, :all, [])) + Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, []))) end def aggregate(queryable, aggregate, field, opts) when aggregate in @aggregates and is_atom(field) and is_list(opts) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.aggregate(get_dynamic_repo(), queryable, aggregate, field, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def exists?(queryable, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Queryable.exists?(get_dynamic_repo(), queryable, triplet(repo, :all, opts)) + Ecto.Repo.Queryable.exists?(get_dynamic_repo(), queryable, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:all, opts))) end def preload(struct_or_structs_or_nil, preloads, opts \\ []) do repo = get_dynamic_repo() - Ecto.Repo.Preloader.preload(struct_or_structs_or_nil, get_dynamic_repo(), preloads, triplet(repo, :preload, opts)) + Ecto.Repo.Preloader.preload(struct_or_structs_or_nil, get_dynamic_repo(), preloads, Ecto.Repo.Supervisor.triplet(repo, prepare_opts(:preload, opts))) end def prepare_query(operation, query, opts), do: {query, opts} diff --git a/lib/ecto/repo/supervisor.ex b/lib/ecto/repo/supervisor.ex index eccfa10a40..ed0bb21b2e 100644 --- a/lib/ecto/repo/supervisor.ex +++ b/lib/ecto/repo/supervisor.ex @@ -154,6 +154,29 @@ defmodule Ecto.Repo.Supervisor do end end + @compile {:inline, triplet: 2, maybe_put_stacktrace: 2} + + @doc false + def triplet(name, opts) do + {adapter, adapter_meta} = Ecto.Repo.Registry.lookup(name) + + {adapter, adapter_meta, maybe_put_stacktrace(opts, adapter_meta)} + end + + defp maybe_put_stacktrace(opts, adapter_meta) do + if opts[:get_stacktrace?] || adapter_meta[:get_stacktrace?] do + stacktrace = + self() + |> Process.info(:current_stacktrace) + |> elem(1) + |> List.delete_at(0) + + Keyword.put(opts, :stacktrace, stacktrace) + else + opts + end + end + ## Callbacks @doc false From 2bdcdec3921655df6cb8a3f565d0099111428ea1 Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Tue, 28 Dec 2021 12:39:08 +0200 Subject: [PATCH 06/11] readd removed test --- integration_test/cases/repo.exs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/integration_test/cases/repo.exs b/integration_test/cases/repo.exs index 12d5fe2066..acffda0bec 100644 --- a/integration_test/cases/repo.exs +++ b/integration_test/cases/repo.exs @@ -18,11 +18,10 @@ defmodule Ecto.Integration.RepoTest do assert {:error, {:already_started, _}} = TestRepo.start_link() end - #TODO: I believe this is supposed to be private API - #test "supports unnamed repos" do - # assert {:ok, pid} = TestRepo.start_link(name: nil) - # assert Ecto.Repo.Queryable.all(pid, Post, []) == [] - #end + test "supports unnamed repos" do + assert {:ok, pid} = TestRepo.start_link(name: nil) + assert Ecto.Repo.Queryable.all(pid, Post, Ecto.Repo.Supervisor.triplet(pid, [])) == [] + end test "all empty" do assert TestRepo.all(Post) == [] From 211be1e2d7a91091c8551d9363474f0652cb1870 Mon Sep 17 00:00:00 2001 From: felipe stival <14948182+v0idpwn@users.noreply.github.com> Date: Sun, 9 Jan 2022 14:08:53 +0200 Subject: [PATCH 07/11] Update lib/ecto/repo.ex --- lib/ecto/repo.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ecto/repo.ex b/lib/ecto/repo.ex index 8b968f585f..f3a4188d62 100644 --- a/lib/ecto/repo.ex +++ b/lib/ecto/repo.ex @@ -252,7 +252,6 @@ defmodule Ecto.Repo do Process.put({__MODULE__, :dynamic_repo}, dynamic) || @default_dynamic_repo end - # temporary: need to set this in repo options and get it here somehow (?) def default_options(_operation), do: [] defoverridable default_options: 1 From 62ec292a8ea67ff1e6fcd102953d253d853f00e3 Mon Sep 17 00:00:00 2001 From: felipe stival <14948182+v0idpwn@users.noreply.github.com> Date: Mon, 10 Jan 2022 15:33:40 +0200 Subject: [PATCH 08/11] Update lib/ecto/repo/supervisor.ex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/ecto/repo/supervisor.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ecto/repo/supervisor.ex b/lib/ecto/repo/supervisor.ex index ed0bb21b2e..c668efd9e7 100644 --- a/lib/ecto/repo/supervisor.ex +++ b/lib/ecto/repo/supervisor.ex @@ -169,7 +169,7 @@ defmodule Ecto.Repo.Supervisor do self() |> Process.info(:current_stacktrace) |> elem(1) - |> List.delete_at(0) + |> tl() Keyword.put(opts, :stacktrace, stacktrace) else From 9301add8aa1c9bfb3443d20509807188c04e2506 Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Mon, 10 Jan 2022 22:29:50 +0200 Subject: [PATCH 09/11] Rename option to `stacktrace` --- lib/ecto/repo/supervisor.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ecto/repo/supervisor.ex b/lib/ecto/repo/supervisor.ex index c668efd9e7..62fd99f63a 100644 --- a/lib/ecto/repo/supervisor.ex +++ b/lib/ecto/repo/supervisor.ex @@ -164,7 +164,7 @@ defmodule Ecto.Repo.Supervisor do end defp maybe_put_stacktrace(opts, adapter_meta) do - if opts[:get_stacktrace?] || adapter_meta[:get_stacktrace?] do + if opts[:stacktrace] || adapter_meta[:stacktrace] do stacktrace = self() |> Process.info(:current_stacktrace) From 93b2bb085a0fa7e82cafe45378e5994c81912089 Mon Sep 17 00:00:00 2001 From: v0idpwn Date: Mon, 10 Jan 2022 22:48:10 +0200 Subject: [PATCH 10/11] Mention new option in the documentation --- lib/ecto/repo.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ecto/repo.ex b/lib/ecto/repo.ex index f3a4188d62..a05de691be 100644 --- a/lib/ecto/repo.ex +++ b/lib/ecto/repo.ex @@ -56,6 +56,9 @@ defmodule Ecto.Repo do use the `:repo` property in the event metadata for distinguishing between repos. + * `:stacktrace`- publishes the stacktrace in telemetry events and + allows more advanced logging. + ## URLs Repositories by default support URLs. For example, the configuration From 1e0255e81e98d62e91aadb355dabdfd61d6f67dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 15 Feb 2022 15:20:10 +0100 Subject: [PATCH 11/11] Update lib/ecto/repo/supervisor.ex --- lib/ecto/repo/supervisor.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ecto/repo/supervisor.ex b/lib/ecto/repo/supervisor.ex index 62fd99f63a..d1a241b656 100644 --- a/lib/ecto/repo/supervisor.ex +++ b/lib/ecto/repo/supervisor.ex @@ -154,7 +154,7 @@ defmodule Ecto.Repo.Supervisor do end end - @compile {:inline, triplet: 2, maybe_put_stacktrace: 2} + @compile {:inline, maybe_put_stacktrace: 2} @doc false def triplet(name, opts) do