Current section

Files

Jump to
ecto lib ecto repo.ex
Raw

lib/ecto/repo.ex

defmodule Ecto.Repo do
@moduledoc """
This module is used to define a repository. A repository maps to a data
store, for example an SQL database. A repository must implement `conf/0` and
set an adapter (see `Ecto.Adapter`) to be used for the repository.
When used, the following options are allowed:
* `:adapter` - the adapter to be used for the repository
* `:env` - configures the repository to support environments
## Example
defmodule MyRepo do
use Ecto.Repo, adapter: Ecto.Adapters.Postgres
def conf do
parse_url "ecto://postgres:postgres@localhost/postgres"
end
end
Most of the time, we want the repository to work with different
environments. In such cases, we can pass an `:env` option:
defmodule MyRepo do
use Ecto.Repo, adapter: Ecto.Adapters.Postgres, env: Mix.env
def conf(env), do: parse_url url(env)
defp url(:dev), do: "ecto://postgres:postgres@localhost/postgres_dev"
defp url(:test), do: "ecto://postgres:postgres@localhost/postgres_test?size=1"
defp url(:prod), do: "ecto://postgres:postgres@localhost/postgres_prod"
end
Notice that, when using the environment, developers should implement
`conf/1` which automatically passes the environment instead of `conf/0`.
Note the environment is only used at compilation time. That said, make
sure the `:build_per_environment` option is set to true (the default)
in your Mix project configuration.
"""
use Behaviour
@type t :: module
@doc false
defmacro __using__(opts) do
adapter = Macro.expand(Keyword.fetch!(opts, :adapter), __CALLER__)
env = Keyword.get(opts, :env)
unless Code.ensure_loaded?(adapter) do
raise ArgumentError, message: "Adapter #{inspect adapter} was not compiled, " <>
"ensure its driver is included as a dependency of your project"
end
quote do
use unquote(adapter)
@behaviour Ecto.Repo
@env unquote(env)
require Logger
import Ecto.Utils, only: [app_dir: 2, parse_url: 1]
if @env do
def conf do
conf(@env)
end
defoverridable conf: 0
end
def start_link do
Ecto.Repo.Backend.start_link(__MODULE__, unquote(adapter))
end
def stop do
Ecto.Repo.Backend.stop(__MODULE__, unquote(adapter))
end
def get(queryable, id, opts \\ []) do
Ecto.Repo.Backend.get(__MODULE__, unquote(adapter), queryable, id, opts)
end
def get!(queryable, id, opts \\ []) do
Ecto.Repo.Backend.get!(__MODULE__, unquote(adapter), queryable, id, opts)
end
def one(queryable, opts \\ []) do
Ecto.Repo.Backend.one(__MODULE__, unquote(adapter), queryable, opts)
end
def one!(queryable, opts \\ []) do
Ecto.Repo.Backend.one!(__MODULE__, unquote(adapter), queryable, opts)
end
def all(queryable, opts \\ []) do
Ecto.Repo.Backend.all(__MODULE__, unquote(adapter), queryable, opts)
end
def insert(model, opts \\ []) do
Ecto.Repo.Backend.insert(__MODULE__, unquote(adapter), model, opts)
end
def update(model, opts \\ []) do
Ecto.Repo.Backend.update(__MODULE__, unquote(adapter), model, opts)
end
defmacro update_all(queryable, values, opts \\ []) do
Ecto.Repo.Backend.update_all(__MODULE__, unquote(adapter), queryable,
values, opts)
end
def delete(model, opts \\ []) do
Ecto.Repo.Backend.delete(__MODULE__, unquote(adapter), model, opts)
end
def delete_all(queryable, opts \\ []) do
Ecto.Repo.Backend.delete_all(__MODULE__, unquote(adapter), queryable, opts)
end
def transaction(opts \\ [], fun) do
Ecto.Repo.Backend.transaction(__MODULE__, unquote(adapter), opts, fun)
end
def rollback(value \\ nil) do
Ecto.Repo.Backend.rollback(__MODULE__, unquote(adapter), value)
end
def adapter do
unquote(adapter)
end
def __repo__ do
true
end
def log({:query, sql}, fun) do
{time, result} = :timer.tc(fun)
Logger.debug fn -> [sql, " (", inspect(time), "µs)"] end
result
end
def log(_arg, fun) do
fun.()
end
def query_apis do
[Ecto.Query.API]
end
defoverridable [log: 2, query_apis: 0]
end
end
@doc """
Should return the database options that will be given to the adapter. Often
used in conjunction with `parse_url/1`. This function must be implemented by
the user.
"""
defcallback conf() :: Keyword.t
@doc """
Starts any connection pooling or supervision and return `{:ok, pid}`
or just `:ok` if nothing needs to be done.
Returns `{:error, {:already_started, pid}}` if the repo already
started or `{:error, term}` in case anything else goes wrong.
"""
defcallback start_link() :: {:ok, pid} | :ok |
{:error, {:already_started, pid}} |
{:error, term}
@doc """
Stops any connection pooling or supervision started with `start_link/1`.
"""
defcallback stop() :: :ok
@doc """
Fetches a single model from the data store where the primary key matches the
given id.
Returns `nil` if no result was found. If the model in the queryable
has no primary key `Ecto.NoPrimaryKey` will be raised. `Ecto.AdapterError`
will be raised if there is an adapter error.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
"""
defcallback get(Ecto.Queryable.t, term, Keyword.t) :: Ecto.Model.t | nil | no_return
@doc """
Similar to `get/3` but raises `Ecto.NotSingleResult` if no record was found.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
"""
defcallback get!(Ecto.Queryable.t, term, Keyword.t) :: Ecto.Model.t | nil | no_return
@doc """
Fetches a single result from the query.
Returns `nil` if no result was found.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
"""
defcallback one(Ecto.Queryable.t, Keyword.t) :: Ecto.Model.t | nil | no_return
@doc """
Similar to `one/3` but raises `Ecto.NotSingleResult` if no record was found.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
"""
defcallback one!(Ecto.Queryable.t, Keyword.t) :: Ecto.Model.t | nil | no_return
@doc """
Fetches all results from the data store based on the given query. May raise
`Ecto.QueryError` if query validation fails. `Ecto.AdapterError` will be
raised if there is an adapter error.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
## Example
# Fetch all post titles
query = from p in Post,
select: p.title
MyRepo.all(query)
"""
defcallback all(Ecto.Query.t, Keyword.t) :: [Ecto.Model.t] | no_return
@doc """
Stores a single new model in the data store and returns its stored
representation. May raise `Ecto.AdapterError` if there is an adapter error.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
## Example
post = %Post{title: "Ecto is great", text: "really, it is"}
|> MyRepo.insert
"""
defcallback insert(Ecto.Model.t, Keyword.t) :: Ecto.Model.t | no_return
@doc """
Updates an model using the primary key as key. If the model has no primary
key `Ecto.NoPrimaryKey` will be raised. `Ecto.AdapterError` will be raised if
there is an adapter error.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
## Example
[post] = from p in Post, where: p.id == 42
post = %{post | title: "New title"}
MyRepo.update(post)
"""
defcallback update(Ecto.Model.t, Keyword.t) :: :ok | no_return
@doc """
Updates all entities matching the given query with the given values.
`Ecto.AdapterError` will be raised if there is an adapter error.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
## Examples
MyRepo.update_all(Post, title: "New title")
MyRepo.update_all(p in Post, visits: p.visits + 1)
from(p in Post, where: p.id < 10)
|> MyRepo.update_all(title: "New title")
"""
defmacrocallback update_all(Macro.t, Keyword.t, Keyword.t) :: integer | no_return
@doc """
Deletes an model using the primary key as key. If the model has no primary
key `Ecto.NoPrimaryKey` will be raised. `Ecto.AdapterError` will be raised if
there is an adapter error.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
## Example
[post] = MyRepo.all(from(p in Post, where: p.id == 42))
MyRepo.delete(post)
"""
defcallback delete(Ecto.Model.t, Keyword.t) :: :ok | no_return
@doc """
Deletes all entities matching the given query with the given values.
`Ecto.AdapterError` will be raised if there is an adapter error.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
## Examples
MyRepo.delete_all(Post)
from(p in Post, where: p.id < 10) |> MyRepo.delete_all
"""
defcallback delete_all(Ecto.Queryable.t, Keyword.t) :: integer | no_return
@doc """
Runs the given function inside a transaction. If an unhandled error occurs the
transaction will be rolled back. If no error occurred the transaction will be
commited when the function returns. A transaction can be explicitly rolled
back by calling `rollback`, this will immediately leave the function and
return the value given to `rollback` as `{:error, value}`. A successful
transaction returns the value returned by the function wrapped in a tuple as
`{:ok, value}`. Transactions can be nested.
## Options
`:timeout` - The time in milliseconds to wait for the call to finish,
`:infinity` will wait indefinitely (default: 5000);
## Examples
MyRepo.transaction(fn ->
MyRepo.update(alice.update_balance(&(&1 - 10))
MyRepo.update(bob.update_balance(&(&1 + 10))
end)
# In the following example only the comment will be rolled back
MyRepo.transaction(fn ->
MyRepo.insert(%Post{})
MyRepo.transaction(fn ->
MyRepo.insert(%Comment{})
raise "error"
end)
end)
# Roll back a transaction explicitly
MyRepo.transaction(fn ->
p = MyRepo.insert(%Post{})
if not Editor.post_allowed?(p) do
MyRepo.rollback
end
end)
"""
defcallback transaction(Keyword.t, fun) :: {:ok, any} | {:error, any}
@doc """
Rolls back the current transaction. See `rollback/1`.
"""
defcallback rollback() :: no_return
@doc """
Rolls back the current transaction. The transaction will return the value
given as `{:error, value}`.
"""
defcallback rollback(any) :: no_return
@doc """
Returns the adapter tied to the repository.
"""
defcallback adapter() :: Ecto.Adapter.t
@doc """
Enables logging and debugging of adapter actions such as sending queries to
the database. Should be overridden to customize behaviour.
You must return the result of calling the passed in function.
## Examples
def log({:query, sql}, fun) do
{time, result} = :timer.tc(fun)
Logger.log({sql, time})
result
end
def log(_arg, fun), do: fun.()
"""
defcallback log(any, (() -> any)) :: any
@doc """
Returns the supported query APIs. Should be overridden to customize.
"""
defcallback query_apis() :: [module]
end