Skip to content

Commit 70ab1c1

Browse files
committed
Add docs to SQL.Adapter default implementations
1 parent 6130b05 commit 70ab1c1

File tree

1 file changed

+183
-23
lines changed

1 file changed

+183
-23
lines changed

lib/ecto/adapters/sql.ex

Lines changed: 183 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -771,69 +771,229 @@ defmodule Ecto.Adapters.SQL do
771771
## Callbacks
772772

773773
@doc false
774-
def __before_compile__(_driver, _env) do
775-
quote do
776-
@doc """
777-
A convenience function for SQL-based repositories that executes the given query.
774+
def __before_compile__(driver, _env) do
775+
default_timeout = @timeout
778776

779-
See `Ecto.Adapters.SQL.query/4` for more information.
777+
quote bind_quoted: [driver: driver, default_timeout: default_timeout] do
778+
@doc """
779+
Runs a custom SQL query.
780+
781+
If the query was successful, it will return an `:ok` tuple containing
782+
a map with at least two keys:
783+
* `:num_rows` - the number of rows affected
784+
* `:rows` - the result set as a list. `nil` may be returned
785+
instead of the list if the command does not yield any row
786+
as result (but still yields the number of affected rows,
787+
like a `delete` command without returning would)
788+
789+
## Options
790+
* `:log` - When false, does not log the query
791+
* `:timeout` - Execute request timeout, accepts: `:infinity` (default: `#{default_timeout}`);
792+
793+
## Examples
794+
iex> MyRepo.query("SELECT $1::integer + $2", [40, 2])
795+
{:ok, %{rows: [[42]], num_rows: 1}}
780796
"""
797+
@spec query(iodata(), Ecto.Adapters.SQL.query_params(), Keyword.t()) ::
798+
{:ok, Ecto.Adapters.SQL.query_result()} | {:error, Exception.t()}
781799
def query(sql, params \\ [], opts \\ []) do
782800
Ecto.Adapters.SQL.query(get_dynamic_repo(), sql, params, opts)
783801
end
784802

785803
@doc """
786-
A convenience function for SQL-based repositories that executes the given query.
804+
Runs a custom SQL query.
787805
788-
See `Ecto.Adapters.SQL.query!/4` for more information.
806+
Same as `query/3` but raises on invalid queries.
789807
"""
808+
@spec query(iodata(), Ecto.Adapters.SQL.query_params(), Keyword.t()) ::
809+
{:ok, Ecto.Adapters.SQL.query_result()} | {:error, Exception.t()}
790810
def query!(sql, params \\ [], opts \\ []) do
791811
Ecto.Adapters.SQL.query!(get_dynamic_repo(), sql, params, opts)
792812
end
793813

794814
@doc """
795-
A convenience function for SQL-based repositories that executes the given multi-result query.
815+
Runs a custom SQL query that returns multiple results on the given repo.
816+
817+
In case of success, it must return an `:ok` tuple containing
818+
a list of maps with at least two keys:
819+
820+
* `:num_rows` - the number of rows affected
821+
822+
* `:rows` - the result set as a list. `nil` may be returned
823+
instead of the list if the command does not yield any row
824+
as result (but still yields the number of affected rows,
825+
like a `delete` command without returning would)
826+
827+
## Options
796828
797-
See `Ecto.Adapters.SQL.query_many/4` for more information.
829+
* `:log` - When false, does not log the query
830+
* `:timeout` - Execute request timeout, accepts: `:infinity` (default: `#{default_timeout}`);
831+
832+
## Examples
833+
834+
iex> MyRepo.query_many("SELECT $1; SELECT $2;", [40, 2])
835+
{:ok, [%{rows: [[40]], num_rows: 1}, %{rows: [[2]], num_rows: 1}]}
798836
"""
837+
838+
@spec query_many(iodata, Ecto.Adapters.SQL.query_params(), Keyword.t()) ::
839+
{:ok, [Ecto.Adapters.SQL.query_result]} | {:error, Exception.t()}
799840
def query_many(sql, params \\ [], opts \\ []) do
800841
Ecto.Adapters.SQL.query_many(get_dynamic_repo(), sql, params, opts)
801842
end
802843

803844
@doc """
804-
A convenience function for SQL-based repositories that executes the given multi-result query.
805-
806-
See `Ecto.Adapters.SQL.query_many!/4` for more information.
845+
Same as `query_many/4` but raises on invalid queries.
807846
"""
847+
@spec query_many!(iodata, Ecto.Adapters.SQL.query_params(), Keyword.t()) ::
848+
[Ecto.Adapters.SQL.query_result]
808849
def query_many!(sql, params \\ [], opts \\ []) do
809850
Ecto.Adapters.SQL.query_many!(get_dynamic_repo(), sql, params, opts)
810851
end
811852

812853
@doc """
813-
A convenience function for SQL-based repositories that translates the given query to SQL.
854+
Converts the given query to SQL according to its kind and the
855+
adapter in the given repository.
856+
857+
## Examples
858+
859+
The examples below are meant for reference. Each adapter will
860+
return a different result:
861+
862+
iex> MyRepo.to_sql(:all, Post)
863+
{"SELECT p.id, p.title, p.inserted_at, p.created_at FROM posts as p", []}
864+
865+
iex> MyRepo.to_sql(:update_all, from(p in Post, update: [set: [title: ^"hello"]]))
866+
{"UPDATE posts AS p SET title = $1", ["hello"]}
814867
815-
See `Ecto.Adapters.SQL.to_sql/3` for more information.
816868
"""
869+
@spec to_sql(:all | :update_all | :delete_all, Ecto.Queryable.t()) ::
870+
{String.t(), Ecto.Adapters.SQL.query_params()}
817871
def to_sql(operation, queryable) do
818872
Ecto.Adapters.SQL.to_sql(operation, get_dynamic_repo(), queryable)
819873
end
820874

821-
@doc """
822-
A convenience function for SQL-based repositories that executes an EXPLAIN statement or similar
823-
depending on the adapter to obtain statistics for the given query.
875+
case driver do
876+
:postgrex ->
877+
@doc """
878+
Executes an EXPLAIN statement or similar for the given query according to its kind and the
879+
adapter in the given repository.
824880
825-
See `Ecto.Adapters.SQL.explain/4` for more information.
826-
"""
881+
## Examples
882+
883+
iex> MyRepo.explain(:all, Post)
884+
"Seq Scan on posts p0 (cost=0.00..12.12 rows=1 width=443)"
885+
886+
# Shared opts
887+
iex> MyRepo.explain(:all, Post, analyze: true, timeout: 20_000)
888+
"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"
889+
890+
It's safe to execute it for updates and deletes, no data change will be committed:
891+
892+
iex> MyRepo.explain(:update_all, from(p in Post, update: [set: [title: "new title"]]))
893+
"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)"
894+
895+
### Options
896+
897+
The built-in Postgrex adapter supports passing `opts` to the EXPLAIN statement according to the following:
898+
`:analyze`, `:verbose`, `:costs`, `:settings`, `:buffers`, `:timing`, `:summary`, `:format`, `:plan`
899+
900+
All options except `format` are boolean valued and default to `false`.
901+
902+
The allowed `format` values are `:map`, `:yaml`, and `:text`:
903+
* `:map` is the deserialized JSON encoding.
904+
* `:yaml` and `:text` return the result as a string.
905+
906+
The Postgrex adapter supports the following formats: `:map`, `:yaml` and `:text`
907+
908+
The `:plan` option in Postgrex can take the values `:custom` or `:fallback_generic`. When `:custom`
909+
is specified, the explain plan generated will consider the specific values of the query parameters
910+
that are supplied. When using `:fallback_generic`, the specific values of the query parameters will
911+
be ignored. `:fallback_generic` does not use PostgreSQL's built-in support for a generic explain
912+
plan (available as of PostgreSQL 16), but instead uses a special implementation that works for PostgreSQL
913+
versions 12 and above. Defaults to `:custom`.
914+
915+
Any other value passed to `opts` will be forwarded to the underlying adapter query function, including
916+
shared Repo options such as `:timeout`. Non built-in adapters may have specific behaviour and you should
917+
consult their documentation for more details.
918+
919+
For version compatibility, please check your database's documentation:
920+
921+
* _Postgrex_: [PostgreSQL doc](https://www.postgresql.org/docs/current/sql-explain.html).
922+
923+
"""
924+
925+
:myxql ->
926+
@doc """
927+
Executes an EXPLAIN statement or similar for the given query according to its kind and the
928+
adapter in the given repository.
929+
930+
## Examples
931+
932+
# MySQL
933+
iex> MyRepo.explain(:all, from(p in Post, where: p.title == "title")) |> IO.puts()
934+
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
935+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
936+
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
937+
| 1 | SIMPLE | p0 | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.0 | Using where |
938+
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
939+
940+
# Shared opts
941+
iex> MyRepo.explain(:all, Post, analyze: true, timeout: 20_000)
942+
"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"
943+
944+
It's safe to execute it for updates and deletes, no data change will be committed:
945+
946+
iex> MyRepo(:update_all, from(p in Post, update: [set: [title: "new title"]]))
947+
"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)"
948+
949+
### Options
950+
951+
The MyXQL adapter supports passing `opts` to the EXPLAIN statement according to the following:
952+
953+
* `:format`
954+
955+
The allowed `format` values are `:map`, `:yaml`, and `:text`:
956+
* `:map` is the deserialized JSON encoding.
957+
* `:yaml` and `:text` return the result as a string.
958+
959+
The built-in adapters support the following formats: `:map` and `:text`
960+
961+
Any other value passed to `opts` will be forwarded to the underlying adapter query function, including
962+
shared Repo options such as `:timeout`. Non built-in adapters may have specific behaviour and you should
963+
consult their documentation for more details.
964+
965+
For version compatibility, please check your database's documentation:
966+
967+
* _MyXQL_: [MySQL doc](https://dev.mysql.com/doc/refman/8.0/en/explain.html).
968+
969+
"""
970+
971+
_ ->
972+
:ok
973+
end
974+
@spec explain(:all | :update_all | :delete_all, Ecto.Queryable.t(), opts :: Keyword.t()) ::
975+
String.t() | Exception.t() | list(map)
827976
def explain(operation, queryable, opts \\ []) do
828977
Ecto.Adapters.SQL.explain(get_dynamic_repo(), operation, queryable, opts)
829978
end
830979

831980
@doc """
832-
A convenience function for SQL-based repositories that forces all connections in the
833-
pool to disconnect within the given interval.
834-
835-
See `Ecto.Adapters.SQL.disconnect_all/3` for more information.
981+
Forces all connections in the repo pool to disconnect within the given interval.
982+
983+
Once this function is called, the pool will disconnect all of its connections
984+
as they are checked in or as they are pinged. Checked in connections will be
985+
randomly disconnected within the given time interval. Pinged connections are
986+
immediately disconnected - as they are idle (according to `:idle_interval`).
987+
988+
If the connection has a backoff configured (which is the case by default),
989+
disconnecting means an attempt at a new connection will be done immediately
990+
after, without starting a new process for each connection. However, if backoff
991+
has been disabled, the connection process will terminate. In such cases,
992+
disconnecting all connections may cause the pool supervisor to restart
993+
depending on the max_restarts/max_seconds configuration of the pool,
994+
so you will want to set those carefully.
836995
"""
996+
@spec disconnect_all(non_neg_integer, opts :: Keyword.t()) :: :ok
837997
def disconnect_all(interval, opts \\ []) do
838998
Ecto.Adapters.SQL.disconnect_all(get_dynamic_repo(), interval, opts)
839999
end

0 commit comments

Comments
 (0)