defmodule Ecto.Adapter.Queryable do @moduledoc """ Specifies the query API required from adapters. If your adapter is only able to respond to one or a couple of the query functions, add custom implementations of those functions directly to the Repo by using `c:Ecto.Adapter.__before_compile__/1` instead. """ @typedoc "Proxy type to the adapter meta" @type adapter_meta :: Ecto.Adapter.adapter_meta() @typedoc "Ecto.Query metadata fields (stored in cache)" @type query_meta :: %{sources: tuple, preloads: term, select: map} @typedoc """ Cache query metadata that is passed to `c:execute/5`. The cache can be in 3 states, documented below. If `{:nocache, prepared}` is given, it means the query was not and cannot be cached. The `prepared` value is the value returned by `c:prepare/2`. If `{:cache, cache_function, prepared}` is given, it means the query can be cached and it must be cached by calling the `cache_function` function with the cache entry of your choice. Once `cache_function` is called, the next time the same query is given to `c:execute/5`, it will receive the `:cached` tuple. If `{:cached, update_function, reset_function, cached}` is given, it means the query has been cached. You may call `update_function/1` if you want to update the cached result. Or you may call `reset_function/1`, with a new prepared query, to force the query to be cached again. If `reset_function/1` is called, the next time the same query is given to `c:execute/5`, it will receive the `:cache` tuple. """ @type query_cache :: {:nocache, prepared} | {:cache, cache_function :: (cached -> :ok), prepared} | {:cached, update_function :: (cached -> :ok), reset_function :: (prepared -> :ok), cached} @type prepared :: term @type cached :: term @type options :: Keyword.t() @type selected :: term @doc """ Commands invoked to prepare a query. It is used on `c:Ecto.Repo.all/2`, `c:Ecto.Repo.update_all/3`, and `c:Ecto.Repo.delete_all/2`. If returns a tuple, saying if this query can be cached or not, and the `prepared` query. The `prepared` query is any term that will be passed to the adapter's `c:execute/5`. """ @callback prepare(atom :: :all | :update_all | :delete_all, query :: Ecto.Query.t()) :: {:cache, prepared} | {:nocache, prepared} @doc """ Executes a previously prepared query. The `query_meta` field is a map containing some of the fields found in the `Ecto.Query` struct, after they have been normalized. For example, the values `selected` by the query, which then have to be returned, can be found in `query_meta`. The `query_cache` and its state is documented in `t:query_cache/0`. The `params` is the list of query parameters. For example, for a query such as `from Post, where: [id: ^123]`, `params` will be `[123]`. Finally, `options` is a keyword list of options given to the `Repo` operation that triggered the adapter call. Any option is allowed, as this is a mechanism to allow users of Ecto to customize how the adapter behaves per operation. It must return a tuple containing the number of entries and the result set as a list of lists. The entries in the actual list will depend on what has been selected by the query. The result set may also be `nil`, if no value is being selected. """ @callback execute(adapter_meta, query_meta, query_cache, params :: list(), options) :: {integer, [[selected]] | nil} @doc """ Streams a previously prepared query. See `c:execute/5` for a description of arguments. It returns a stream of values. """ @callback stream(adapter_meta, query_meta, query_cache, params :: list(), options) :: Enumerable.t @doc """ Plans and prepares a query for the given repo, leveraging its query cache. This operation uses the query cache if one is available. """ def prepare_query(operation, repo_name_or_pid, queryable) do {adapter, %{cache: cache}} = Ecto.Repo.Registry.lookup(repo_name_or_pid) {_meta, prepared, params} = queryable |> Ecto.Queryable.to_query() |> Ecto.Query.Planner.ensure_select(operation == :all) |> Ecto.Query.Planner.query(operation, cache, adapter, 0) {prepared, params} end @doc """ Plans a query using the given adapter. This does not expect the repository and therefore does not leverage the cache. """ def plan_query(operation, adapter, queryable) do query = Ecto.Queryable.to_query(queryable) {query, params, _key} = Ecto.Query.Planner.plan(query, operation, adapter) {query, _} = Ecto.Query.Planner.normalize(query, operation, adapter, 0) {query, params} end end