Skip to content

Better docs for Repos that use Ecto.Adapters.SQL.Adapter #671

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

dbernheisel
Copy link
Contributor

@dbernheisel dbernheisel commented Jun 1, 2025

The functions added by Ecto.Adapters.SQL.Adapter.__before_compile__ into Repos have some sparse documentation telling the user to go look elsewhere to the underlying function; this makes it harder for folks using LSPs and and their cursor is on MyApp.query(...) to get any helpful documentation, instead requiring them to either open docs outside of their editor, or have a temporary line pointing to Ecto.Adapters.SQL.query(...) and get the docs, and then set back to MyApp.query(...)

I also took the opportunity to generate different docs since it knows the adapter being used; so a Postgres-adapter-using Repo will only have Postgres-related docs, same with MyXQL.

Before

iex(1)> h Foo.Repo.explain

                 def explain(operation, queryable, opts \\ [])                  

A convenience function for SQL-based repositories that executes an EXPLAIN
statement or similar depending on the adapter to obtain statistics for the
given query.

See Ecto.Adapters.SQL.explain/4 for more information.

iex(2)> h Foo.MySQLRepo.explain

                 def explain(operation, queryable, opts \\ [])                  

A convenience function for SQL-based repositories that executes an EXPLAIN
statement or similar depending on the adapter to obtain statistics for the
given query.

See Ecto.Adapters.SQL.explain/4 for more information.

After

iex(2)> h Foo.Repo.explain

                 def explain(operation, queryable, opts \\ [])                  

  @spec explain(
          :all | :update_all | :delete_all,
          Ecto.Queryable.t(),
          opts :: Keyword.t()
        ) :: String.t() | Exception.t() | [map()]

Executes an EXPLAIN statement or similar for the given query according to its
kind and the adapter in the given repository.

## Examples

    iex> MyRepo.explain(:all, Post)
    "Seq Scan on posts p0  (cost=0.00..12.12 rows=1 width=443)"
    
    # Shared opts
    iex> MyRepo.explain(:all, Post, analyze: true, timeout: 20_000)
    "Seq Scan on posts p0  (cost=0.00..11.70 rows=170 width=443) (actual time=0.013..0.013 rows=0 loops=1)\nPlanning Time: 0.031 ms\nExecution Time: 0.021 ms"

It's safe to execute it for updates and deletes, no data change will be
committed:

    iex> MyRepo.explain(:update_all, from(p in Post, update: [set: [title: "new title"]]))
    "Update on posts p0  (cost=0.00..11.70 rows=170 width=449)\n  ->  Seq Scan on posts p0 (cost=0.00..11.70 rows=170 width=449)"

### Options

The built-in Postgrex adapter supports passing opts to the EXPLAIN statement
according to the following:

    `:analyze`, `:verbose`, `:costs`, `:settings`, `:buffers`, `:timing`, `:summary`, `:format`, `:plan`

All options except format are boolean valued and default to false.

The allowed format values are :map, :yaml, and :text:

  • :map is the deserialized JSON encoding.
  • :yaml and :text return the result as a string.

The Postgrex adapter supports the following formats: :map, :yaml and :text

The :plan option in Postgrex can take the values :custom or :fallback_generic.
When :custom is specified, the explain plan generated will consider the
specific values of the query parameters that are supplied. When using
:fallback_generic, the specific values of the query parameters will be ignored.
:fallback_generic does not use PostgreSQL's built-in support for a generic
explain plan (available as of PostgreSQL 16), but instead uses a special
implementation that works for PostgreSQL versions 12 and above. Defaults to
:custom.

Any other value passed to opts will be forwarded to the underlying adapter
query function, including shared Repo options such as :timeout. Non built-in
adapters may have specific behaviour and you should consult their documentation
for more details.

For version compatibility, please check your database's documentation:

  • _Postgrex_: PostgreSQL doc
    (https://www.postgresql.org/docs/current/sql-explain.html).
iex(3)> h Foo.MySQLRepo.explain

                 def explain(operation, queryable, opts \\ [])                  

  @spec explain(
          :all | :update_all | :delete_all,
          Ecto.Queryable.t(),
          opts :: Keyword.t()
        ) :: String.t() | Exception.t() | [map()]

Executes an EXPLAIN statement or similar for the given query according to its
kind and the adapter in the given repository.

## Examples

    # MySQL
    iex> MyRepo.explain(:all, from(p in Post, where: p.title == "title")) |> IO.puts()
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+--
--------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | f
iltered | Extra       |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+--
--------+-------------+
    |  1 | SIMPLE      | p0    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |  
  100.0 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+--
--------+-------------+
    
    # Shared opts
    iex> MyRepo.explain(:all, Post, analyze: true, timeout: 20_000)
    "Seq Scan on posts p0  (cost=0.00..11.70 rows=170 width=443) (actual time=0.013..0.013 rows=0 loops=1)\nPlanning Time: 0.031 ms\nExecution Time: 0.021 ms"

It's safe to execute it for updates and deletes, no data change will be
committed:

    iex> MyRepo(:update_all, from(p in Post, update: [set: [title: "new title"]]))
    "Update on posts p0  (cost=0.00..11.70 rows=170 width=449)\n  ->  Seq Scan on posts p0  (cost=0.00..11.70 rows=170 width=449)"

### Options

The MyXQL adapter supports passing opts to the EXPLAIN statement according to
the following:

  • :format

The allowed format values are :map, :yaml, and :text:

  • :map is the deserialized JSON encoding.
  • :yaml and :text return the result as a string.

The built-in adapters support the following formats: :map and :text

Any other value passed to opts will be forwarded to the underlying adapter
query function, including shared Repo options such as :timeout. Non built-in
adapters may have specific behaviour and you should consult their documentation
for more details.

For version compatibility, please check your database's documentation:

  • _MyXQL_: MySQL doc
    (https://dev.mysql.com/doc/refman/8.0/en/explain.html).

@josevalim
Copy link
Member

Thank you! In order to remove the duplication, I think you could do this:

doc_disconnect_all = """
...
"""

and then use it for disconnect all, as in @doc disconnect_all and inside the __using__ like this:

@doc unquote(doc_disconnect_all)

WDYT?

@dbernheisel
Copy link
Contributor Author

Seems obvious now :) yep I'll rework that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants