# `Lotus.Source.Adapters.Ecto`
[🔗](https://github.com/elixir-lotus/lotus/blob/v1.2.0/lib/lotus/source/adapters/ecto.ex#L1)

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 `c: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
    end

The `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`.

# `dynamic_state`

```elixir
@type dynamic_state() :: %{
  :repo =&gt; module(),
  :dynamic =&gt; dynamic_target(),
  optional(atom()) =&gt; term()
}
```

A repo module started at runtime, reached through `put_dynamic_repo/1`.

# `dynamic_target`

```elixir
@type dynamic_target() ::
  pid() | atom() | {:global, term()} | {:via, module(), term()}
```

Where a dynamic repo runs: what `c:Ecto.Repo.put_dynamic_repo/1` takes, or
a process name that `GenServer.whereis/1` resolves on every call.

# `state`

```elixir
@type state() :: module() | dynamic_state()
```

The adapter state: a statically supervised repo module or a dynamic repo.

# `builtin_adapters`

Returns the list of built-in per-dialect Ecto adapter modules.

Used by `Lotus.Source.Resolvers.Static` to avoid duplicating this list.

# `can_handle?`

```elixir
@spec can_handle?(term()) :: boolean()
```

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.

# `detect_source_type`

```elixir
@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

# `repo_module`

```elixir
@spec repo_module(state()) :: module()
```

Returns the repo module of an adapter state, for either shape.

# `with_repo`

```elixir
@spec with_repo(state(), (module() -&gt; result)) :: result when result: term()
```

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, `c: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)

# `wrap`

```elixir
@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}, ...}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
