Skip to content

Commit

Permalink
Add Ecto.Repo.all/0
Browse files Browse the repository at this point in the history
This can be used by projects like Phoenix.LiveDashboard
to automatically list all Ecto repositories.
  • Loading branch information
josevalim committed May 27, 2021
1 parent 8ebfabb commit 42e3e69
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 15 deletions.
4 changes: 2 additions & 2 deletions lib/ecto/query/planner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ defmodule Ecto.Query.Planner do
@doc """
Define the query cache table.
"""
def new_query_cache(name) do
:ets.new(name, [:set, :public, read_concurrency: true])
def new_query_cache(atom_name) do
:ets.new(atom_name || __MODULE__, [:set, :public, read_concurrency: true])
end

@doc """
Expand Down
10 changes: 10 additions & 0 deletions lib/ecto/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ defmodule Ecto.Repo do

@type t :: module

@doc """
Returns all running Ecto repositories.
The list is returned in no particular order. The list
contains either atoms, for named Ecto repositories, or
PIDs.
"""
@spec all() :: [atom() | pid()]
defdelegate all(), to: Ecto.Repo.Registry

@doc false
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
Expand Down
21 changes: 12 additions & 9 deletions lib/ecto/repo/registry.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
defmodule Ecto.Repo.Registry do
@moduledoc false

# TODO: Use persistent_term when depending on Erlang/OTP 22+
use GenServer

## Public interface

def start_link(_opts) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end

def associate(pid, value) when is_pid(pid) do
GenServer.call(__MODULE__, {:associate, pid, value})
def associate(pid, name, value) when is_pid(pid) do
GenServer.call(__MODULE__, {:associate, pid, name, value})
end

def all() do
for [pid, name] <- :ets.match(__MODULE__, {:"$1", :_, :"$2", :_}) do
name || pid
end
end

def lookup(repo) when is_atom(repo) do
Expand All @@ -21,7 +24,7 @@ defmodule Ecto.Repo.Registry do
end

def lookup(pid) when is_pid(pid) do
:ets.lookup_element(__MODULE__, pid, 3)
:ets.lookup_element(__MODULE__, pid, 4)
end

## Callbacks
Expand All @@ -33,15 +36,15 @@ defmodule Ecto.Repo.Registry do
end

@impl true
def handle_call({:associate, pid, value}, _from, table) do
def handle_call({:associate, pid, name, value}, _from, table) do
ref = Process.monitor(pid)
true = :ets.insert(table, {pid, ref, value})
true = :ets.insert(table, {pid, ref, name, value})
{:reply, :ok, table}
end

@impl true
def handle_info({:DOWN, ref, _type, pid, _reason}, table) do
[{^pid, ^ref, _}] = :ets.lookup(table, pid)
[{^pid, ^ref, _, _}] = :ets.lookup(table, pid)
:ets.delete(table, pid)
{:noreply, table}
end
Expand Down
12 changes: 8 additions & 4 deletions lib/ecto/repo/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ defmodule Ecto.Repo.Supervisor do
Starts the repo supervisor.
"""
def start_link(repo, otp_app, adapter, opts) do
sup_opts = if name = Keyword.get(opts, :name, repo), do: [name: name], else: []
name = Keyword.get(opts, :name, repo)
sup_opts = if name, do: [name: name], else: []
Supervisor.start_link(__MODULE__, {name, repo, otp_app, adapter, opts}, sup_opts)
end

Expand Down Expand Up @@ -157,6 +158,9 @@ defmodule Ecto.Repo.Supervisor do

@doc false
def init({name, repo, otp_app, adapter, opts}) do
# Normalize name to atom, ignore via/global names
name = if is_atom(name), do: name, else: nil

case runtime_config(:supervisor, repo, otp_app, opts) do
{:ok, opts} ->
:telemetry.execute(
Expand All @@ -168,19 +172,19 @@ defmodule Ecto.Repo.Supervisor do
{:ok, child, meta} = adapter.init([repo: repo] ++ opts)
cache = Ecto.Query.Planner.new_query_cache(name)
meta = Map.merge(meta, %{repo: repo, cache: cache})
child_spec = wrap_child_spec(child, [adapter, meta])
child_spec = wrap_child_spec(child, [name, adapter, meta])
Supervisor.init([child_spec], strategy: :one_for_one, max_restarts: 0)

:ignore ->
:ignore
end
end

def start_child({mod, fun, args}, adapter, meta) do
def start_child({mod, fun, args}, name, adapter, meta) do
case apply(mod, fun, args) do
{:ok, pid} ->
meta = Map.put(meta, :pid, pid)
Ecto.Repo.Registry.associate(self(), {adapter, meta})
Ecto.Repo.Registry.associate(self(), name, {adapter, meta})
{:ok, pid}

other ->
Expand Down
8 changes: 8 additions & 0 deletions test/ecto/repo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1505,4 +1505,12 @@ defmodule Ecto.RepoTest do
assert_received {:transaction, _}
end
end

describe "all" do
test "lists all repositories" do
assert Ecto.Repo.all() == [Ecto.TestRepo]
pid = start_supervised! {Ecto.TestRepo, name: nil}
assert Enum.sort(Ecto.Repo.all()) == [Ecto.TestRepo, pid]
end
end
end

0 comments on commit 42e3e69

Please sign in to comment.