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

The SQL-specific half of an Ecto-backed data source.

`Lotus.Source.Adapter` is the adapter-agnostic contract every data source
implements. A dialect is narrower: it supplies the parts that differ between
*SQL* engines behind an `Ecto.Repo` — identifier quoting, parameter
placeholders, `EXPLAIN` syntax, catalogue introspection queries, and the
built-in deny rules for each engine's system tables.

Pair a dialect with the `Lotus.Source.Adapters.Ecto` macro and the adapter
is a one-liner:

    defmodule LotusMSSql.Dialect do
      @behaviour Lotus.Source.Adapters.Ecto.Dialect

      # ... callbacks ...
    end

    defmodule LotusMSSql.Adapter do
      use Lotus.Source.Adapters.Ecto, dialect: LotusMSSql.Dialect
    end

The macro injects every `Lotus.Source.Adapter` callback, routing the shared
Ecto plumbing through `Lotus.Source.Adapters.Ecto` and the engine-specific
parts through your dialect. All of them are `defoverridable`, so a dialect
that needs to escape the macro's assumptions can implement the adapter
callback directly.

## When not to write a dialect

A dialect is for SQL over Ecto. A data source that is not SQL, or not
reached through an `Ecto.Repo`, implements `Lotus.Source.Adapter` directly
instead — see the [Source Adapters guide](source-adapters.md). Elasticsearch
is the worked example of that path.

## Statement shape

Pipeline callbacks take and return `%Lotus.Query.Statement{}`. For a
dialect, `statement.body` is always SQL text and `statement.params` the
bound values in the order the driver expects.

## Optional callbacks

Everything in `@optional_callbacks` has a safe default, so a minimal
dialect only implements what its engine actually needs. Notably
`set_statement_timeout/2` and `set_search_path/2` are optional: engines
with no session-level timeout or schema search path simply omit them
rather than defining no-op clauses.

# `repo`

```elixir
@type repo() :: Ecto.Repo.t()
```

# `ai_context`
*optional* 

```elixir
@callback ai_context() :: {:ok, Lotus.Source.Adapter.ai_context_map()} | {:error, term()}
```

Return the adapter-shaped AI context for this dialect.

Supplies the four `Lotus.Source.Adapter.ai_context_map` keys —
`:language`, `:example_query`, `:syntax_notes`, `:error_patterns` —
with dialect-specific content. The built-in SQL dialects populate
Postgres / MySQL / SQLite quirks here.

Default (when not implemented): generic-SQL context synthesized from
`query_language/0`, with an empty error-pattern list.

# `apply_filters`

```elixir
@callback apply_filters(
  statement :: Lotus.Query.Statement.t(),
  filters :: [Lotus.Query.Filter.t()]
) :: Lotus.Query.Statement.t()
```

# `apply_sorts`

```elixir
@callback apply_sorts(
  statement :: Lotus.Query.Statement.t(),
  sorts :: [Lotus.Query.Sort.t()]
) ::
  Lotus.Query.Statement.t()
```

# `builtin_denies`

```elixir
@callback builtin_denies(repo()) :: [
  {String.t() | nil | Regex.t(), String.t() | Regex.t()}
]
```

# `builtin_schema_denies`

```elixir
@callback builtin_schema_denies(repo()) :: [String.t() | Regex.t()]
```

# `db_type_to_lotus_type`
*optional* 

```elixir
@callback db_type_to_lotus_type(db_type :: String.t()) :: atom()
```

Map a database-specific column type string to a Lotus internal type atom.

Each dialect knows its own type system (e.g. Postgres `uuid`, MySQL `char(36)`,
SQLite `INTEGER`).

Default (when not implemented): `:text`.

# `default_schemas`

```elixir
@callback default_schemas(repo()) :: [String.t()]
```

# `describe_table`

```elixir
@callback describe_table(repo(), schema :: String.t() | nil, table :: String.t()) :: [
  %{
    name: String.t(),
    type: String.t(),
    nullable: boolean(),
    default: String.t() | nil,
    primary_key: boolean()
  }
]
```

# `ecto_adapter`

```elixir
@callback ecto_adapter() :: module() | nil
```

# `editor_config`
*optional* 

```elixir
@callback editor_config() :: %{
  :language =&gt; String.t(),
  :keywords =&gt; [String.t()],
  :types =&gt; [String.t()],
  :functions =&gt; [%{name: String.t(), detail: String.t(), args: String.t()}],
  :context_boundaries =&gt; [String.t()],
  optional(:dialect_spec) =&gt; Lotus.Source.Adapter.dialect_spec(),
  optional(:context_schema) =&gt; Lotus.Source.Adapter.context_schema()
}
```

# `example_query`
*optional* 

```elixir
@callback example_query(table :: String.t(), schema :: String.t() | nil) :: String.t()
```

# `execute_in_transaction`

```elixir
@callback execute_in_transaction(repo(), (-&gt; any()), keyword()) ::
  {:ok, any()} | {:error, any()}
```

# `extract_accessed_resources`
*optional* 

```elixir
@callback extract_accessed_resources(repo(), statement :: Lotus.Query.Statement.t()) ::
  {:ok, MapSet.t({String.t() | nil, String.t()})}
  | {:error, term()}
  | {:unrestricted, String.t()}
```

Extract the set of tables/relations a statement will access.

Used by `Lotus.Preflight` to check visibility rules before execution.
Return `{:ok, MapSet}` with `{schema, table}` tuples, `{:error, reason}`,
or `{:unrestricted, reason}` when the dialect cannot enforce visibility
at this layer.

Default (when not implemented): `{:unrestricted, ...}`.

# `format_error`

```elixir
@callback format_error(any()) :: String.t()
```

# `hierarchy_label`
*optional* 

```elixir
@callback hierarchy_label() :: String.t()
```

# `limit_offset_placeholders`

```elixir
@callback limit_offset_placeholders(
  limit_index :: pos_integer(),
  offset_index :: pos_integer()
) :: {limit_placeholder :: String.t(), offset_placeholder :: String.t()}
```

# `limit_query`

```elixir
@callback limit_query(statement :: Lotus.Query.Statement.t(), limit :: pos_integer()) ::
  Lotus.Query.Statement.t()
```

# `list_schemas`

```elixir
@callback list_schemas(repo()) :: [String.t()]
```

# `list_tables`

```elixir
@callback list_tables(repo(), schemas :: [String.t()], include_views? :: boolean()) :: [
  {schema :: String.t() | nil, table :: String.t()}
]
```

# `needs_preflight?`
*optional* 

```elixir
@callback needs_preflight?(statement :: Lotus.Query.Statement.t()) :: boolean()
```

Whether the statement needs the visibility preflight check before execution.

Used by the Ecto adapter's built-in `needs_preflight?/2` default to skip
introspection statements (`EXPLAIN`, `SHOW`, `PRAGMA`) that don't access
visible relations. Dialects may override with a language-specific check.

Default (when not implemented): `true` (always preflight).

# `param_placeholder`

```elixir
@callback param_placeholder(
  index :: pos_integer(),
  var :: String.t(),
  type :: atom() | nil
) :: String.t()
```

# `query_language`

```elixir
@callback query_language() :: String.t()
```

# `query_plan`

```elixir
@callback query_plan(repo(), statement :: Lotus.Query.Statement.t(), opts :: keyword()) ::
  {:ok, String.t() | nil} | {:error, term()}
```

# `quote_identifier`

```elixir
@callback quote_identifier(String.t()) :: String.t()
```

# `resolve_table_namespace`

```elixir
@callback resolve_table_namespace(repo(), table :: String.t(), schemas :: [String.t()]) ::
  String.t() | nil
```

# `set_search_path`
*optional* 

```elixir
@callback set_search_path(repo(), String.t()) :: :ok | no_return()
```

Apply a session-level schema search path.

Optional. Called inside the query transaction when the caller supplies
`:search_path`. Engines without a search path omit it, and Lotus ignores
the option for that source.

# `set_statement_timeout`
*optional* 

```elixir
@callback set_statement_timeout(repo(), non_neg_integer()) :: :ok | no_return()
```

Apply a session-level statement timeout.

Optional. Engines with no such notion omit it and Lotus relies on the
driver-level `:timeout` passed to `execute_query/4` instead.

# `source_type`

```elixir
@callback source_type() :: :postgres | :mysql | :sqlite | :other | atom()
```

# `supports_feature?`
*optional* 

```elixir
@callback supports_feature?(feature :: atom()) :: boolean()
```

# `transform_statement`
*optional* 

```elixir
@callback transform_statement(statement :: Lotus.Query.Statement.t()) ::
  Lotus.Query.Statement.t()
```

Rewrite the statement before variables are extracted and bound.

Fires in `Lotus.Storage.Query.compile/2` before `{{var}}` placeholders
are resolved. Use for dialect-specific syntax rewrites (wildcard handling,
INTERVAL, CONCAT vs `||`, etc.). Return the rewritten statement.

Default (when not implemented): statement unchanged.

---

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