Adapter wrapping any Ecto.Repo module in the Lotus.Source.Adapter behaviour.
Delegates database-specific operations to dialect modules under
Lotus.Source.Adapters.Ecto.Dialects.* based on the repo's underlying
Ecto adapter.
Usage
adapter = Lotus.Source.Adapters.Ecto.wrap("main", MyApp.Repo)
Lotus.Source.Adapter.execute_query(adapter, "SELECT 1", [], [])
Lotus.Source.Adapter.list_schemas(adapter)The state field of the resulting %Adapter{} struct holds the repo module
itself, since Ecto repos are statically supervised and don't require explicit
connection management.
Dynamic repos
A repo started at runtime without a name (MyApp.Repo.start_link(name: nil))
is reachable only through Ecto.Repo.put_dynamic_repo/1, which applies to
the calling process. Pass the state as a map to have the adapter make that
call around every repo operation:
adapter =
Lotus.Source.Adapters.Ecto.wrap("tenant", %{repo: MyApp.Repo, dynamic: pid}):repo is the repo module the process was started from. :dynamic is
what put_dynamic_repo/1 takes, a pid or an atom, or a {:via, _, _} or
{:global, _} name that the adapter resolves to a pid on every call, so a
repo started under Lotus.Source.Registry.via/2 can be wrapped before it
is running. The previous dynamic repo of the calling process is restored
after each call. with_repo/2 is the helper the callbacks use and is
available to adapters that override one of them.
Extending with custom Ecto-backed adapters
External libraries can create Ecto-backed adapters by writing a dialect module and a one-liner adapter:
defmodule LotusMSSql.Adapter do
use Lotus.Source.Adapters.Ecto, dialect: LotusMSSql.Dialect
endThe use macro injects default implementations for all
Lotus.Source.Adapter callbacks, delegating shared Ecto logic to helper
functions in this module and dialect-specific callbacks to the provided
:dialect module. All callbacks are defoverridable.
Summary
Types
A repo module started at runtime, reached through put_dynamic_repo/1.
Where a dynamic repo runs: what Ecto.Repo.put_dynamic_repo/1 takes, or
a process name that GenServer.whereis/1 resolves on every call.
The adapter state: a statically supervised repo module or a dynamic repo.
Functions
Returns the list of built-in per-dialect Ecto adapter modules.
Whether this adapter can handle the given data source entry.
Detects the source type from a repo module's underlying Ecto adapter.
Returns the repo module of an adapter state, for either shape.
Runs fun with the repo module of state, routed to the dynamic repo
when state carries one.
Wraps an Ecto.Repo module in an %Adapter{} struct.
Types
@type dynamic_state() :: %{ :repo => module(), :dynamic => dynamic_target(), optional(atom()) => term() }
A repo module started at runtime, reached through put_dynamic_repo/1.
Where a dynamic repo runs: what Ecto.Repo.put_dynamic_repo/1 takes, or
a process name that GenServer.whereis/1 resolves on every call.
@type state() :: module() | dynamic_state()
The adapter state: a statically supervised repo module or a dynamic repo.
Functions
Returns the list of built-in per-dialect Ecto adapter modules.
Used by Lotus.Source.Resolvers.Static to avoid duplicating this list.
Whether this adapter can handle the given data source entry.
Returns true for any module that exports __adapter__/0, and for the
%{repo: module, dynamic: _} form when its :repo does. This is
intentionally broad — it acts as a catch-all fallback for Ecto repos
that don't match a more specific per-dialect adapter. The resolver
checks per-dialect adapters first (via builtin_adapters/0 and
source_adapters config), so this only matches repos with unknown
Ecto adapters.
@spec detect_source_type(module()) :: Lotus.Source.Adapter.source_type()
Detects the source type from a repo module's underlying Ecto adapter.
Examples
iex> Lotus.Source.Adapters.Ecto.detect_source_type(MyApp.Repo)
:postgres
Returns the repo module of an adapter state, for either shape.
Runs fun with the repo module of state, routed to the dynamic repo
when state carries one.
For a repo module the function is called with the module as is. For a
%{repo: module, dynamic: target} state, the target is resolved to a pid
or an atom, Ecto.Repo.put_dynamic_repo/1 is called on the repo module,
fun runs, and the dynamic repo the calling process had before is put
back, whether fun returns or raises. A {:via, _, _} or {:global, _}
target whose process is not running raises ArgumentError.
Examples
Lotus.Source.Adapters.Ecto.with_repo(MyApp.Repo, & &1.query!("SELECT 1"))
Lotus.Source.Adapters.Ecto.with_repo(%{repo: MyApp.Repo, dynamic: pid}, fn repo ->
repo.query!("SELECT 1")
end)
@spec wrap(String.t(), state()) :: Lotus.Source.Adapter.t()
Wraps an Ecto.Repo module in an %Adapter{} struct.
The resulting adapter delegates all callbacks to the appropriate source implementation based on the repo's underlying Ecto adapter.
Parameters
name— a human-readable identifier (e.g."main","warehouse")state— the Ecto.Repo module (e.g.MyApp.Repo), or%{repo: MyApp.Repo, dynamic: pid_or_name}for a repo started at runtime (see the "Dynamic repos" section)
Examples
iex> adapter = Lotus.Source.Adapters.Ecto.wrap("main", MyApp.Repo)
%Lotus.Source.Adapter{name: "main", module: Lotus.Source.Adapters.Ecto, ...}
iex> Lotus.Source.Adapters.Ecto.wrap("tenant", %{repo: MyApp.Repo, dynamic: pid})
%Lotus.Source.Adapter{name: "tenant", state: %{repo: MyApp.Repo, dynamic: pid}, ...}