Spec-Zone.ru › Phoenix

Ecto.Repo поведение

Исходный код

Определяет репозиторий.

Репозиторий сопоставляется с базовым хранилищем данных, управляемым адаптером. Например, Ecto поставляется с адаптером Postgres, который хранит данные в базе данных PostgreSQL.

При использовании репозиторий ожидает :otp_app и :adapter в качестве параметров. :otp_app должен указывать на приложение OTP, в котором находится конфигурация репозитория. Например, репозиторий:

defmodule Repo do
  use Ecto.Repo,
    otp_app: :my_app,
    adapter: Ecto.Adapters.Postgres
end

Можно настроить следующим образом:

config :my_app, Repo,
  database: "ecto_simple",
  username: "postgres",
  password: "postgres",
  hostname: "localhost"

Большая часть конфигурации, передаваемой в config, зависит от адаптера. В данном конкретном примере дополнительную информацию можно найти в Ecto.Adapters.Postgres. Тем не менее следующие значения конфигурации являются общими для всех адаптеров:

  • :name- Имя супервизорного процесса Repo. Обратите внимание, что оно должно быть уникальным среди всех модулей репозиториев

  • :priv — каталог для хранения данных репозитория, например миграций, схем и прочего. По умолчанию используется "priv/YOUR_REPO". Он всегда должен указывать на подкаталог внутри каталога priv

  • :url — URL, указывающий сведения о хранилище. Дополнительную информацию см. ниже

  • :log — уровень журнала, используемый при записи запроса с помощью Logger в Elixir. Может принимать любое из значений Logger.level/0 или false. Значение false отключает ведение журнала для этого репозитория. По умолчанию используется :debug

  • :pool_size — размер пула, используемого модулем соединения. По умолчанию используется 10

  • :pool_count — количество пулов, работающих одновременно. Увеличьте это значение, если сам пул может испытывать высокую нагрузку. При использовании нескольких пулов запросы случайным образом направляются в разные пулы без учета количества доступных соединений в каждом из них. Поэтому в некоторых случаях запрос может быть направлен в полностью занятый пул, в то время как в других пулах есть свободные соединения. Общее количество используемых соединений составит pool_size * pool_count. По умолчанию используется 1

  • :telemetry_prefix — мы рекомендуем адаптерам публиковать события с помощью библиотеки Telemetry. По умолчанию префикс telemetry формируется на основе имени модуля: если модуль называется MyApp.Repo, префиксом будет [:my_app, :repo]. Список событий, которые мы рекомендуем публиковать адаптерам, см. в разделе «События Telemetry». Обратите внимание: если у вас несколько баз данных, для каждого репозитория следует использовать одинаковое значение :telemetry_prefix, а для различения репозиториев — свойство :repo в метаданных события.

  • :stacktrace- если значение равно true, публикует трассировку стека в событиях telemetry и позволяет использовать более подробное ведение журнала.

  • :log_stacktrace_mfa — кортеж {module, function, arguments}, задающий, какая часть трассировки стека включается в журналы запросов. Указанная функция должна принимать не менее двух аргументов (трассировку стека и метаданные) и возвращать отфильтрованную трассировку стека. Метаданные представляют собой карту с ключами, такими как :repo, и другими сведениями, специфичными для адаптера. Дополнительные аргументы можно передать третьим элементом кортежа. Для Ecto.Adapters.SQL по умолчанию используется {Ecto.Adapters.SQL, :first_non_ecto_stacktrace, [1]}, которая фильтрует трассировку стека, оставляя только первый вызов, исходящий за пределами внутреннего кода Ecto. Имеет значение, только если :stacktrace равно true.

URL-адреса

По умолчанию репозитории поддерживают URL-адреса. Например, конфигурацию выше можно переписать так:

config :my_app, Repo,
  url: "ecto://postgres:postgres@localhost/ecto_simple"

Схема может иметь любое значение, а путь обозначает имя базы данных. URL будет использоваться для создания соответствующих значений конфигурации Repo, таких как :database, :username, :password, :hostname и :port. Эти значения имеют приоритет над уже указанными в конфигурации Repo.

URL может содержать параметры запроса для переопределения общих параметров и параметров, специфичных для адаптера, таких как ssl, timeout и pool_size. В следующем примере показано, как передать эти значения конфигурации:

config :my_app, Repo,
  url: "ecto://postgres:postgres@localhost/ecto_simple?ssl=true&pool_size=10"

Поддержка IPv6

Если хост вашей базы данных разрешается в IPv6-адрес, добавьте socket_options: [:inet6] в блок конфигурации, как показано ниже:

import Mix.Config

config :my_app, MyApp.Repo,
  hostname: "db12.dc0.comp.any",
  socket_options: [:inet6],
  ...

Параметры use

При использовании use Ecto.Repo поддерживаются следующие параметры:

  • :otp_app (обязательный) — имя приложения Erlang/OTP, в котором следует искать конфигурацию репозитория (обычно это имя приложения Elixir)

  • :adapter (обязательный) — модуль адаптера базы данных, который вы хотите использовать

  • :read_only — если значение равно true, помечает репозиторий как :read_only. В таком случае функции, выполняющие операции записи, такие как insert/2, insert_all/3, update_all/3 и другие, не определяются

Общие параметры

Почти все функции репозитория, описанные в этом модуле, принимают следующие параметры:

  • :timeout — время в миллисекундах (целое число), в течение которого ожидается завершение вызова запроса. :infinity будет ожидать бесконечно (по умолчанию: 15_000)
  • :log — может принимать любое из значений Logger.level/0 или false. Если значение равно false, ведение журнала отключено. По умолчанию используется настроенный уровень журналирования Repo
  • :telemetry_event — имя события telemetry, под которым следует отправить событие. Дополнительную информацию см. в следующем разделе
  • :telemetry_options — дополнительные параметры, прикрепляемые к имени события telemetry. Дополнительную информацию см. в следующем разделе
  • :query_cache — если установлено значение false, для текущей операции обходится кэш запросов Ecto. Это означает, что запрос не будет искаться в кэше, сохраняться в нём, а адаптеру не будет передана функция обновления кэша. Обратите внимание, что это не обязательно отключает кэш базы данных — затрагивается только внутренний кэш Ecto нормализованных запросов и подготовленных инструкций адаптера. По умолчанию используется true.

Ошибки, специфичные для адаптера

Многие функции этого модуля могут вызывать ошибки, специфичные для адаптера, например PostgrexError. Это может произойти, например, если базовая база данных не может выполнить указанный запрос.

События Telemetry

Существует два типа событий telemetry: события, отправляемые Ecto, и события, специфичные для адаптера.

События telemetry Ecto

Все репозитории Ecto отправляют следующие события:

  • [:ecto, :repo, :init] — вызывается при каждом запуске репозитория. Измерение представляет собой одну запись system_time в нативных единицах. Метаданные включают :repo и все параметры инициализации из :opts.

События, специфичные для адаптера

Мы рекомендуем адаптерам публиковать некоторые события Telemetry, перечисленные ниже. Для этих событий будет использоваться описанный выше :telemetry_prefix, значение которого по умолчанию — [:my_app, :repo].

Например, чтобы получать все события запросов, публикуемые репозиторием с именем MyApp.Repo, можно определить модуль:

defmodule MyApp.Telemetry do
  def handle_event([:my_app, :repo, :query], measurements, metadata, config) do
    IO.inspect binding()
  end
end

Затем в обратном вызове Application.start/2 прикрепите обработчик к этому событию, указав уникальный идентификатор обработчика:

:ok = :telemetry.attach("my-app-handler-id", [:my_app, :repo, :query], &MyApp.Telemetry.handle_event/4, %{})

Подробную информацию см. в документации по telemetry.

Ниже перечислены все события, которые разработчики могут ожидать от Ecto. Во всех приведённых ниже примерах предполагается, что репозиторий называется MyApp.Repo:

[:my_app, :repo, :query]

Это событие должно вызываться для каждого запроса, отправленного адаптеру, включая запросы, связанные с управлением транзакциями.

Карта :measurements может включать перечисленные ниже значения, каждое из которых задано в единицах времени :native:

  • :idle_time — время, в течение которого соединение ожидало перед получением для выполнения запроса
  • :queue_time — время ожидания получения соединения с базой данных
  • :query_time — время выполнения запроса
  • :decode_time — время декодирования данных, полученных из базы данных
  • :total_time — сумма значений (queue_time, query_time и decode_time)️

Все измерения заданы в единицах времени :native. Подробнее об этом см. в документации к System.convert_time_unit/3.

Карта :metadata telemetry включает следующие поля. Каждый адаптер базы данных может предоставлять здесь разные данные. Для баз данных Ecto.SQL она будет выглядеть так:

  • :type — тип запроса Ecto. Например, для баз данных Ecto.SQL это будет :ecto_sql_query
  • :repo — репозиторий Ecto
  • :result — результат запроса
  • :params — выгруженные параметры запроса (в формате, подходящем для драйверов баз данных, таких как Postgrex)
  • :cast_params — приведённые типы параметров запроса (нормализованные перед выгрузкой)
  • :query — запрос, отправленный в базу данных в виде строки
  • :source — источник, для которого был выполнен запрос (может быть nil)
  • :stacktrace — сведения о трассировке стека, если она включена, или nil
  • :options — дополнительные параметры, переданные операции репозитория в :telemetry_options

Сводка

API запросов

aggregate(queryable, aggregate, opts)

Вычисляет заданный aggregate.

aggregate(queryable, aggregate, field, opts)

Вычисляет заданный aggregate для указанного field.

all(queryable, opts)

Извлекает из хранилища данных все записи, соответствующие заданному запросу.

all_by(queryable, clauses, opts)

Извлекает из хранилища данных все записи, соответствующие заданному запросу и условиям.

delete_all(queryable, opts)

Удаляет все записи, соответствующие заданному запросу.

exists?(queryable, opts)

Проверяет, существует ли запись, соответствующая заданному запросу.

get(queryable, id, opts)

Извлекает из хранилища данных одну структуру, первичный ключ которой совпадает с заданным идентификатором.

get!(queryable, id, opts)

Аналогично get/3, но вызывает исключение Ecto.NoResultsError, если запись не найдена.

get_by(queryable, clauses, opts)

Извлекает один результат запроса.

get_by!(queryable, clauses, opts)

Аналогично get_by/3, но вызывает исключение Ecto.NoResultsError, если запись не найдена.

one(queryable, opts)

Извлекает один результат запроса.

one!(queryable, opts)

Аналогично one/2, но вызывает исключение Ecto.NoResultsError, если запись не найдена.

stream(queryable, opts)

Возвращает ленивый перечислимый объект, выдающий все записи из хранилища данных, соответствующие заданному запросу.

update_all(queryable, updates, opts)

Обновляет все записи, соответствующие заданному запросу, указанными значениями.

API схемы

delete(struct_or_changeset, opts)

Удаляет структуру, используя её первичный ключ.

delete!(struct_or_changeset, opts)

То же, что и delete/2, но возвращает структуру или вызывает исключение, если набор изменений недействителен.

insert(struct_or_changeset, opts)

Вставляет структуру, определённую с помощью Ecto.Schema, или набор изменений.

insert!(struct_or_changeset, opts)

То же, что и insert/2, но возвращает структуру или вызывает исключение, если набор изменений недействителен.

insert_all(schema_or_source, entries_or_query, opts)

Вставляет все записи в репозиторий.

insert_or_update(changeset, opts)

Вставляет или обновляет набор изменений в зависимости от того, сохранена ли структура.

insert_or_update!(changeset, opts)

То же, что и insert_or_update/2, но возвращает структуру или вызывает исключение, если набор изменений недействителен.

load(schema_or_map, data)

Загружает data в схему или карту.

preload(structs_or_struct_or_nil, preloads, opts)

Предварительно загружает все ассоциации для указанной структуры или структур.

reload(struct_or_structs, opts)

Повторно загружает указанную схему или список схем из базы данных.

reload!(struct_or_structs, opts)

Аналогично reload/2, но вызывает исключение, если что-либо не найдено.

update(changeset, opts)

Обновляет набор изменений, используя его первичный ключ.

update!(changeset, opts)

То же, что и update/2, но возвращает структуру или вызывает исключение, если набор изменений недействителен.

API транзакций

checked_out?()

Возвращает true, если соединение было получено из пула.

checkout(function, opts)

Получает соединение из пула на время выполнения функции.

in_transaction?()

Возвращает true, если текущий процесс находится внутри транзакции.

rollback(value)

Откатывает текущую транзакцию.

transact(fun, opts)

Выполняет заданную функцию или Ecto.Multi внутри транзакции.

transaction(fun_or_multi, opts) устарело

Выполняет заданную функцию или Ecto.Multi внутри транзакции.

API процессов

all_running()

Возвращает все работающие репозитории Ecto.

get_dynamic_repo()

Возвращает атом с именем или PID текущего репозитория.

put_dynamic_repo(name_or_pid)

Задаёт динамический репозиторий, используемый при последующих взаимодействиях.

start_link(opts)

Запускает дерево супервизии Repo.

stop(timeout)

Останавливает репозиторий.

API конфигурации

__adapter__()

Возвращает адаптер, связанный с репозиторием.

config()

Возвращает конфигурацию адаптера, сохранённую в окружении :otp_app.

Пользовательские обратные вызовы

default_options(operation)

Пользовательский обратный вызов, вызываемый для получения параметров операций по умолчанию.

init(context, config)

Обратный вызов, выполняемый при запуске репозитория или чтении конфигурации.

prepare_query(operation, query, opts)

Пользовательский обратный вызов, вызываемый для операций на основе запросов.

prepare_transaction(fun_or_multi, opts)

Пользовательский обратный вызов, вызываемый при выполнении операций с транзакциями.

Типы

t()

API запросов

Функции, работающие с Ecto.Query.

aggregate(queryable, aggregate, opts)Source

@callback aggregate(
  queryable :: Ecto.Queryable.t(),
  aggregate :: :count,
  opts :: Keyword.t()
) :: term() | nil

Вычисляет заданную aggregate.

Любые preload или select в запросе будут проигнорированы в пользу агрегируемого столбца. Однако, если в запросе заданы limit, offset, distinct или комбинация, он будет автоматически обёрнут в подзапрос, чтобы вернуть правильный результат. При этом поле select должно соответствовать определённым правилам: возвращать source, поле (например, source.field) или map с атомарными ключами и скалярными значениями (целыми числами, числами с плавающей точкой и строками) либо простыми выражениями. Эти правила применяются ко всем подзапросам в Ecto.

Агрегирование завершится с ошибкой, если задано поле group_by.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Примеры

# Returns the number of blog posts
Repo.aggregate(Post, :count)

# Returns the number of blog posts in the "private" schema path
# (in Postgres) or database (in MySQL)
Repo.aggregate(Post, :count, prefix: "private")

aggregate(queryable, aggregate, field, opts)Source

@callback aggregate(
  queryable :: Ecto.Queryable.t(),
  aggregate :: :avg | :count | :max | :min | :sum,
  field :: atom(),
  opts :: Keyword.t()
) :: term() | nil

Вычисляет заданную aggregate для указанного field.

Общие рекомендации и параметры см. в разделе aggregate/3.

Примеры

# Returns the sum of the number of visits for every blog post
Repo.aggregate(Post, :sum, :visits)

# Returns the sum of the number of visits for every blog post in the
# "private" schema path (in Postgres) or database (in MySQL)
Repo.aggregate(Post, :sum, :visits, prefix: "private")

# Returns the average number of visits for the first 10 blog posts
query = from Post, limit: 10
Repo.aggregate(query, :avg, :visits)

all(queryable, opts)Source

@callback all(queryable :: Ecto.Queryable.t(), opts :: Keyword.t()) :: [
  Ecto.Schema.t() | term()
]

Извлекает из хранилища данных все записи, соответствующие заданному запросу.

Если проверка запроса завершится с ошибкой, может быть вызвано исключение Ecto.QueryError.

См. также all_by/3, one/2 и get/3.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

# Fetch all post titles
query = from p in Post,
          select: p.title
MyRepo.all(query)

all_by(queryable, clauses, opts)Source

@callback all_by(
  queryable :: Ecto.Queryable.t(),
  clauses :: Keyword.t() | map(),
  opts :: Keyword.t()
) :: [Ecto.Schema.t() | term()]

Извлекает из хранилища данных все записи, соответствующие заданному запросу и условиям.

Если проверка запроса завершится с ошибкой, может быть вызвано исключение Ecto.QueryError.

Эта функция служит сокращённой записью для all/2, когда заданный запрос нужно дополнить простыми условиями.

См. также all/2 и get_by/3.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

MyRepo.all_by(Post, author_id: 1)

query = from p in Post
MyRepo.all_by(query, author_id: 1)

delete_all(queryable, opts)Source

@callback delete_all(queryable :: Ecto.Queryable.t(), opts :: Keyword.t()) ::
  {non_neg_integer(), nil | [term()]}

Удаляет все записи, соответствующие заданному запросу.

Возвращает кортеж, содержащий количество записей и результат, если он был получен, в качестве второго элемента. По умолчанию второй элемент — nil, если только в запросе на удаление не задан select. Однако следует учитывать, что не все базы данных поддерживают возврат данных при выполнении DELETE.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Остальные параметры см. в разделе «Общие параметры» документации модуля.

Примеры

MyRepo.delete_all(Post)

from(p in Post, where: p.id < 10) |> MyRepo.delete_all()

# With returning results, if supported by the database.
{_count, posts} = from(p in Post, where: p.id < 10, select: p) |> MyRepo.delete_all()

exists?(queryable, opts)Source

@callback exists?(queryable :: Ecto.Queryable.t(), opts :: Keyword.t()) :: boolean()

Проверяет, существует ли запись, соответствующая заданному queryable.

Возвращает логическое значение.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Сгенерированный запрос

Ecto изменит переданный queryable, чтобы максимально сократить его. Например, принудительно задав SELECT 1 и LIMIT 1. Любые дополнительные условия фильтрации нужно указывать непосредственно в queryable с помощью таких выражений, как where и having.

Примеры

# checks if any posts exist
Repo.exists?(Post)

# checks if any posts exist in the "private" schema path (in Postgres) or
# database (in MySQL)
Repo.exists?(Post, prefix: "private")

# checks if any post with a like count greater than 10 exists
query = from p in Post, where: p.like_count > 10
Repo.exists?(query)

get(queryable, id, opts)Source

@callback get(queryable :: Ecto.Queryable.t(), id :: term(), opts :: Keyword.t()) ::
  Ecto.Schema.t() | term() | nil

Извлекает из хранилища данных единственную структуру, первичный ключ которой совпадает с заданным идентификатором.

Если результат не найден, возвращает nil. Если у структуры в queryable нет первичного ключа или их больше одного, будет вызвана ошибка аргумента.

См. также get!/3, one/2 и all_by/3.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

MyRepo.get(Post, 42)

MyRepo.get(Post, 42, prefix: "public")

get!(queryable, id, opts)Source

@callback get!(queryable :: Ecto.Queryable.t(), id :: term(), opts :: Keyword.t()) ::
  Ecto.Schema.t() | term()

Аналогично get/3, но если запись не найдена, вызывает исключение Ecto.NoResultsError.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

MyRepo.get!(Post, 42)

MyRepo.get!(Post, 42, prefix: "public")

get_by(queryable, clauses, opts)Source

@callback get_by(
  queryable :: Ecto.Queryable.t(),
  clauses :: Keyword.t() | map(),
  opts :: Keyword.t()
) :: Ecto.Schema.t() | term() | nil

Извлекает из запроса единственный результат.

Если результат не найден, возвращает nil. Если найдено больше одной записи, вызывает исключение.

См. также get/3, one/2 и all_by/3.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

MyRepo.get_by(Post, title: "My post")

MyRepo.get_by(Post, [title: "My post"], prefix: "public")

get_by!(queryable, clauses, opts)Source

@callback get_by!(
  queryable :: Ecto.Queryable.t(),
  clauses :: Keyword.t() | map(),
  opts :: Keyword.t()
) :: Ecto.Schema.t() | term()

Аналогично get_by/3, но если запись не найдена, вызывает исключение Ecto.NoResultsError.

Если найдено больше одной записи, вызывает исключение.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

MyRepo.get_by!(Post, title: "My post")

MyRepo.get_by!(Post, [title: "My post"], prefix: "public")

one(queryable, opts)Source

@callback one(queryable :: Ecto.Queryable.t(), opts :: Keyword.t()) ::
  Ecto.Schema.t() | term() | nil

Извлекает из запроса единственный результат.

Если результат не найден, возвращает nil. Если найдено больше одной записи, вызывает исключение.

См. также one!/2, get/3 и all/2.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Примеры

Repo.one(from p in Post, join: c in assoc(p, :comments), where: p.id == ^post_id)

query = from p in Post, join: c in assoc(p, :comments), where: p.id == ^post_id
Repo.one(query, prefix: "private")

one!(queryable, opts)Source

@callback one!(queryable :: Ecto.Queryable.t(), opts :: Keyword.t()) ::
  Ecto.Schema.t() | term()

Аналогично one/2, но если запись не найдена, вызывает исключение Ecto.NoResultsError.

Если найдено больше одной записи, вызывает исключение.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

stream(queryable, opts)Source

@callback stream(queryable :: Ecto.Queryable.t(), opts :: Keyword.t()) :: Enum.t()

Возвращает ленивый перечислимый объект, выдающий все записи из хранилища данных, соответствующие заданному запросу.

Адаптеры SQL, такие как Postgres и MySQL, позволяют перебирать поток только внутри транзакции.

Если проверка запроса завершится с ошибкой, может быть вызвано исключение Ecto.QueryError.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

  • :max_rows — количество строк, загружаемых из базы данных при потоковой обработке. Поддерживается как минимум в Postgres и MySQL; значение по умолчанию — 500.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

# Fetch all post titles
query = from p in Post, select: p.title

stream = MyRepo.stream(query)

MyRepo.transact(fn ->
  Enum.to_list(stream)
end)

update_all(queryable, updates, opts)Source

@callback update_all(
  queryable :: Ecto.Queryable.t(),
  updates :: Keyword.t(),
  opts :: Keyword.t()
) :: {non_neg_integer(), nil | [term()]}

Обновляет все записи, соответствующие заданному запросу, указанными значениями.

Возвращает кортеж, содержащий количество записей и результат, если он был получен, в качестве второго элемента. По умолчанию второй элемент — nil, если только в запросе на обновление не задан select. Однако следует учитывать, что не все базы данных поддерживают возврат данных при выполнении UPDATE.

Обратите внимание: эта update_all не обновляет автоматически генерируемые поля, например столбцы updated_at.

Сведения об операциях обновления полей см. в разделе Ecto.Query.update/3.

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он будет применён ко всем from и joins в запросе, для которых префикс ранее не был задан ни через параметр :prefix в join/from, ни через @schema_prefix в схеме. Дополнительные сведения см. в разделе «Префикс запроса» документации Ecto.Query.

Остальные параметры см. в разделе «Общие параметры» документации модуля.

Примеры

MyRepo.update_all(Post, set: [title: "New title"])

MyRepo.update_all(Post, inc: [visits: 1])

from(p in Post, where: p.id < 10, select: p.visits)
|> MyRepo.update_all(set: [title: "New title"])

from(p in Post, where: p.id < 10, update: [set: [title: "New title"]])
|> MyRepo.update_all([])

from(p in Post, where: p.id < 10, update: [set: [title: ^new_title]])
|> MyRepo.update_all([])

from(p in Post, where: p.id < 10, update: [set: [title: fragment("upper(?)", ^new_title)]])
|> MyRepo.update_all([])

from(p in Post, where: p.id < 10, update: [set: [visits: p.visits * 1000]])
|> MyRepo.update_all([])

API схем

Функции, работающие со Ecto.Schema или Ecto.Changeset.

delete(struct_or_changeset, opts)Исходный код

@callback delete(
  struct_or_changeset :: Ecto.Schema.t() | Ecto.Changeset.t(),
  opts :: Keyword.t()
) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Удаляет структуру с использованием её первичного ключа.

Если у структуры нет первичного ключа, будет вызвана ошибка Ecto.NoPrimaryKeyFieldError. Если структура была удалена до вызова, будет вызвана ошибка Ecto.StaleEntryError. Если требуется более одной операции с базой данных, они автоматически выполняются в транзакции.

Функция возвращает {:ok, struct}, если структура была успешно удалена, или {:error, changeset}, если произошла ошибка валидации или известная ошибка ограничения. По умолчанию ошибки ограничений вызывают исключение Ecto.ConstraintError, если только первым аргументом не передан changeset с объявленными в нём соответствующими ограничениями (см. Ecto.Changeset).

Параметры

  • :returning — определяет, какие поля нужно вернуть. Принимает список полей, которые нужно вернуть из базы данных. Если указано true, возвращает все поля, включая помеченные как load_in_query: false. Если указано false, дополнительные поля не возвращаются. Всегда включает все поля в read_after_writes. Также принимает {:unsafe_fragment, fragment}, чтобы напрямую передать необработанное SQL-выражение в предложение RETURNING (без экранирования; используйте с осторожностью). Обратите внимание, что поля, возвращённые из базы данных, заменяют значения, переданные пользователем. Любое поле, не возвращённое базой данных, будет содержать исходное значение, переданное пользователем. Не все базы данных поддерживают этот параметр.

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Переопределяет префикс, заданный в запросе, и любой @schema_prefix, заданный в схеме.

  • :stale_error_field — поле, в которое будут добавлены ошибки устаревших данных в возвращаемом changeset. Этот параметр позволяет избежать вызова Ecto.StaleEntryError.

  • :stale_error_message — сообщение, добавляемое в настроенный :stale_error_field при возникновении ошибок устаревших данных; по умолчанию — "is stale".

  • :allow_stale — не вызывает ошибку, если удаляемая запись устарела. По умолчанию — false. Это может произойти, если структура была удалена из базы данных до этого удаления или если правило либо триггер базы данных отклоняет операцию удаления.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

post = MyRepo.get!(Post, 42)
case MyRepo.delete(post) do
  {:ok, struct}       -> # Deleted with success
  {:error, changeset} -> # Something went wrong
end

delete!(struct_or_changeset, opts)Исходный код

@callback delete!(
  struct_or_changeset :: Ecto.Schema.t() | Ecto.Changeset.t(),
  opts :: Keyword.t()
) :: Ecto.Schema.t()

То же, что и delete/2, но возвращает структуру или вызывает исключение, если changeset недействителен.

insert(struct_or_changeset, opts)Исходный код

@callback insert(
  struct_or_changeset :: Ecto.Schema.t() | Ecto.Changeset.t(),
  opts :: Keyword.t()
) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Вставляет структуру, определённую с помощью Ecto.Schema, или changeset.

Если передана структура, она преобразуется в changeset со всеми ненулевыми полями в качестве изменений.

Если передан changeset, изменения в нём объединяются с полями структуры, после чего всё отправляется в базу данных. Если требуется более одной операции с базой данных, они автоматически выполняются в транзакции.

Функция возвращает {:ok, struct}, если структура была успешно вставлена, или {:error, changeset}, если произошла ошибка валидации или известная ошибка ограничения.

Параметры

  • :returning — определяет, какие поля нужно вернуть. Принимает список полей, которые нужно вернуть из базы данных. Если указано true, возвращает все поля, включая помеченные как load_in_query: false. Если указано false, дополнительные поля не возвращаются. Всегда включает все поля в read_after_writes, а также любой автоматически сгенерированный идентификатор. Также принимает {:unsafe_fragment, fragment}, чтобы напрямую передать необработанное SQL-выражение в предложение RETURNING (без экранирования; используйте с осторожностью). Обратите внимание, что поля, возвращённые из базы данных, заменяют значения, переданные пользователем. Любое поле, не возвращённое базой данных, будет содержать исходное значение, переданное пользователем. Не все базы данных поддерживают этот параметр, и он может быть недоступен при выполнении upsert. Подробнее см. в разделе «Upsert».

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Переопределяет префикс, заданный в запросе, и любой @schema_prefix, заданный в схемах. Кроме того, @schema_prefix родительской записи переопределяет все значения @schema_prefix по умолчанию, заданные в дочерних схемах связей.

  • :on_conflict — может принимать одно из значений :raise (по умолчанию), :nothing, :replace_all, {:replace_all_except, fields}, {:replace, fields}, список ключевых слов с инструкциями обновления или запрос Ecto.Query для обновления. Подробнее см. в разделе «Upsert».

  • :conflict_target — список имён столбцов для проверки конфликтов. Предполагается, что для этих столбцов созданы уникальные индексы, с которыми может возникнуть конфликт. Если параметр не указан, цель конфликта определяется базой данных. Также может принимать {:unsafe_fragment, binary_fragment}, чтобы передать в базу данных любое выражение без очистки; это полезно для частичных индексов или индексов с выражениями, например {:unsafe_fragment, "(coalesce(firstname, ""), coalesce(lastname, "")) WHERE middlename IS NULL"} для SQL-запроса ON CONFLICT (coalesce(firstname, ""), coalesce(lastname, "")) WHERE middlename IS NULL.

  • :replace_changed — определяет, включать ли поля :conflict_target, когда :on_conflict равно :replace_all или {:replace_all_except, fields}. Если указано true, конфликтующие поля не обновляются, что позволяет использовать такие оптимизации, как HOT-обновления в PostgreSQL. По умолчанию — true.

  • :stale_error_field — поле, в которое будут добавлены ошибки устаревших данных в возвращаемом changeset. Этот параметр позволяет избежать вызова Ecto.StaleEntryError.

  • :stale_error_message — сообщение, добавляемое в настроенный :stale_error_field при возникновении ошибок устаревших данных; по умолчанию — "is stale".

  • :allow_stale — не вызывает ошибку, если структуры устарели. По умолчанию — false. Это может произойти, если правила или триггеры базы данных отклоняют операцию вставки. Параметр распространяется на связи.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Примеры

Типичный пример — вызов MyRepo.insert/1 со структурой и обработка возвращаемого значения:

case MyRepo.insert(%Post{title: "Ecto is great"}) do
  {:ok, struct}       -> # Inserted with success
  {:error, changeset} -> # Something went wrong
end

Upsert

insert/2 поддерживает upsert (обновление или вставку) с помощью параметра :on_conflict. Параметр :on_conflict поддерживает следующие значения:

  • :raise — вызывает ошибку при конфликте первичного ключа или уникального индекса

  • :nothing — игнорирует ошибку при конфликте

  • :replace_all — заменяет все значения существующей строки значениями из схемы/changeset, включая поля, явно не заданные в changeset, например идентификаторы и автоматически сгенерированные временные метки (inserted_at и updated_at). Не используйте этот параметр, если у вас есть автоинкрементные первичные ключи: они также будут заменены. Скорее всего, вам стоит вместо этого явно использовать {:replace_all_except, [:id]} или {:replace, fields}. Для этого параметра требуется схема. Поля, указанные в :conflict_target, будут проигнорированы, если только :replace_changed не настроен как false

  • {:replace_all_except, fields} — то же, что и выше, но указанные поля не заменяются. Для этого параметра требуется схема

  • {:replace, fields} — заменяет только определённые столбцы. Для этого параметра требуется :conflict_target

  • список ключевых слов с инструкциями обновления — например, тот, который передаётся в update_all/3, например: [set: [title: "new title"]]

  • запрос Ecto.Query, который будет использоваться как инструкция UPDATE, например, передаваемая в update_all/3. Как и в случае с update_all/3, автоматически сгенерированные значения, например временные метки, не обновляются автоматически. Если структура не найдена, будет вызвана ошибка Ecto.StaleEntryError.

В таких базах данных, как Postgres, upsert преобразуется в "ON CONFLICT", а в таких, как MySQL, — в "ON DUPLICATE KEY".

Например, предположим, что :title помечен в базе данных как уникальный столбец:

{:ok, inserted} = MyRepo.insert(%Post{title: "this is unique"})

Теперь можно вставить запись с тем же заголовком и ничего не делать при конфликте:

{:ok, ignored} = MyRepo.insert(%Post{title: "this is unique"}, on_conflict: :nothing)

Поскольку мы использовали on_conflict: :nothing, вместо ошибки получили {:ok, struct}. Однако возвращённая структура не отражает данные в базе данных. Если первичный ключ генерируется базой данных автоматически, первичный ключ в записи ignored будет равен nil, если вставка не произошла. Например, если используется первичный ключ по умолчанию (с именем :id и типом :id), то указанный выше ignored.id будет равен nil, если вставка не произошла.

Если идентификатор генерируется приложением (обычно так бывает для :binary_id) или если для :on_conflict передано другое значение, определить, произошла ли вставка или обновление, немного сложнее, так как база данных фактически не сообщает нам, что произошло. Давайте вставим запись с тем же заголовком, но при конфликте используем запрос для обновления столбца body:

# In Postgres (it requires the conflict target for updates):
on_conflict = [set: [body: "updated"]]
{:ok, updated} = MyRepo.insert(%Post{title: "this is unique"},
                               on_conflict: on_conflict, conflict_target: :title)

# In MySQL (conflict target is not supported):
on_conflict = [set: [title: "updated"]]
{:ok, updated} = MyRepo.insert(%Post{id: inserted.id, title: "updated"},
                               on_conflict: on_conflict)

В приведённых выше примерах, хотя функция и вернула :ok, мы не знаем, вставили ли мы новые данные или обновили только поля :on_conflict. Если произошло обновление, данные в структуре, скорее всего, не совпадают с данными в базе данных. Например, автоматически сгенерированные поля, такие как inserted_at, будут указывать на текущее время, а не на время фактической вставки структуры.

Если нужно гарантировать, что данные в возвращённой структуре соответствуют базе данных, есть три варианта:

  • Использовать on_conflict: :replace_all, хотя это заменит все поля в базе данных значениями из структуры/changeset, включая автоматически сгенерированные поля, такие как inserted_at и updated_at:

    MyRepo.insert(%Post{title: "this is unique"},
                  on_conflict: :replace_all, conflict_target: :title)
  • Указать read_after_writes: true в схеме, чтобы выбрать поля, считываемые из базы данных после каждой операции. Либо передать returning: true в insert, чтобы считать все поля. (Обратите внимание: чтение из базы данных произойдёт, только если обновлено хотя бы одно поле.)

    MyRepo.insert(%Post{title: "this is unique"}, returning: true,
                  on_conflict: on_conflict, conflict_target: :title)
  • В качестве альтернативы можно повторно считать данные из базы данных отдельным запросом. Для этого варианта первичный ключ должен генерироваться базой данных:

    {:ok, updated} = MyRepo.insert(%Post{title: "this is unique"}, on_conflict: on_conflict)
    Repo.get(Post, updated.id)

Поскольку невозможно определить, актуальна ли структура, не рекомендуется одновременно вставлять структуру со связями и использовать параметр :on_conflict: Ecto не сможет правильно отслеживать состояние связи.

Расширенные сценарии upsert

Использование запроса Ecto.Query для :on_conflict позволяет задействовать более сложные возможности базы данных. Например, PostgreSQL поддерживает условные операции upsert, такие как DO UPDATE SET title = EXCLUDED.title, version = EXCLUDED.version WHERE EXCLUDED.version > post.version. Это означает, что заголовок и версия будут обновлены, только если версия предлагаемой строки выше версии существующей строки.

Ecto поддерживает такой сценарий следующим образом:

conflict_query =
  from(p in Post,
    update: [set: [
      title: fragment("EXCLUDED.title"),
      version: fragment("EXCLUDED.version")
      ]],
    where: fragment("EXCLUDED.version > ?", p.version)
  )

MyRepo.insert(
  %Post{id: 1, title: "Ecto Upserts (Dance Remix)", version: 2},
  conflict_target: [:id],
  on_conflict: conflict_query
)

insert!(struct_or_changeset, opts)Исходный код

@callback insert!(
  struct_or_changeset :: Ecto.Schema.t() | Ecto.Changeset.t(),
  opts :: Keyword.t()
) :: Ecto.Schema.t()

То же, что и insert/2, но возвращает структуру или вызывает исключение, если changeset недействителен.

insert_all(schema_or_source, entries_or_query, opts)Исходный код

@callback insert_all(
  schema_or_source :: binary() | {binary(), module()} | module(),
  entries_or_query ::
    [%{required(atom() | String.t()) => value} | Keyword.t(value)]
    | Ecto.Query.t(),
  opts :: Keyword.t()
) :: {non_neg_integer(), nil | [term()]}
when value: term() | Ecto.Query.t()

Вставляет все записи в репозиторий.

Первым аргументом ожидается модуль схемы (MyApp.User), источник ("users") или и то и другое ({"users", MyApp.User}). Второй аргумент — список вставляемых записей в виде списков ключевых слов или карт. Если первым аргументом указан модуль схемы, ключи записей — это имена полей в виде атомов. В противном случае ключами могут быть атомы или строки, представляющие имена столбцов в базовом хранилище данных. Значением должно быть соответствующее значение типа поля или, по желанию, запрос Ecto.Query, возвращающий одну запись с одним значением.

Функция возвращает кортеж, содержащий количество записей и результат, если он был возвращён, в качестве второго элемента. Если база данных не поддерживает RETURNING в инструкциях INSERT или результат не был выбран для возврата, второй элемент будет равен nil.

Если передан модуль схемы, записи будут корректно преобразованы перед отправкой в базу данных. Если тип первичного ключа схемы — :id или :binary_id, его обработка выполняется на уровне адаптера или хранилища. Однако ключи других типов и автоматически генерируемые значения, такие как Ecto.UUID и временные метки, не будут автоматически сгенерированы при использовании insert_all/3. Эти поля необходимо задавать явно. Это сделано намеренно: данная функция обеспечивает более прямой способ вставки данных в базу данных без дополнительных удобств insert/2. Это также согласуется с update_all/3, которая также не обрабатывает автоматически генерируемые значения.

Кроме того, использовать insert_all для вставки данных в несколько таблиц невозможно, поэтому связи не поддерживаются.

Если передан источник без модуля схемы, указанные поля передаются адаптеру без изменений.

Параметры

  • :returning — определяет, какие поля нужно вернуть. Если указано true, возвращаются все поля заданной схемы. Можно указать список полей: структура будет возвращена, но только с заданными полями. Или false, при котором ничего не возвращается (значение по умолчанию). Также принимает {:unsafe_fragment, fragment}, чтобы напрямую передать необработанное SQL-выражение в предложение RETURNING (без экранирования; используйте с осторожностью). Этот параметр поддерживается не всеми базами данных.

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Переопределяет префикс, заданный в запросе, и любой @schema_prefix, заданный в схеме.

  • :on_conflict — может принимать одно из значений :raise (по умолчанию), :nothing, :replace_all, {:replace_all_except, fields}, {:replace, fields}, список ключевых слов с инструкциями обновления или запрос Ecto.Query для обновления. Подробнее см. в разделе «Upsert».

  • :conflict_target — список имён столбцов для проверки конфликтов. Предполагается, что для этих столбцов созданы уникальные индексы, с которыми может возникнуть конфликт. Если параметр не указан, цель конфликта определяется базой данных. Также может принимать {:unsafe_fragment, binary_fragment}, чтобы передать в базу данных любое выражение без очистки; это полезно для частичных индексов или индексов с выражениями, например {:unsafe_fragment, "(coalesce(firstname, ''), coalesce(lastname, '')) WHERE middlename IS NULL"} для SQL-запроса ON CONFLICT (coalesce(firstname, ''), coalesce(lastname, '')) WHERE middlename IS NULL.

  • :replace_changed — определяет, включать ли поля :conflict_target, когда :on_conflict равно :replace_all или {:replace_all_except, fields}. Если указано true, поля, заданные в качестве цели конфликта, не обновляются, что позволяет использовать такие оптимизации, как HOT-обновления в PostgreSQL. По умолчанию — true.

  • :placeholders — карта с заполнителями. Эта возможность поддерживается не всеми базами данных. Подробнее см. в разделе «Заполнители».

Остальные параметры см. в разделе «Общие параметры» документации модуля.

Запрос-источник

Вместо списка записей можно передать запрос. Он должен выбирать карту, содержащую только ключи, соответствующие доступным для записи столбцам схемы. Этот запрос выберет и вставит значения за один запрос, без дополнительного обмена данными с приложением.

Примеры

MyRepo.insert_all(Post, [[title: "My first post"], [title: "My second post"]])

MyRepo.insert_all(Post, [%{title: "My first post"}, %{title: "My second post"}])

query = from p in Post,
  join: c in assoc(p, :comments),
  select: %{
    author_id: p.author_id,
    posts: count(p.id, :distinct),
    interactions: sum(p.likes) + count(c.id)
  },
  group_by: p.author_id
MyRepo.insert_all(AuthorStats, query)

Upsert

insert_all/3 поддерживает upsert (обновление или вставку) с помощью параметра :on_conflict. Параметр :on_conflict поддерживает следующие значения:

  • :raise — вызывает ошибку при конфликте первичного ключа или уникального индекса

  • :nothing — игнорирует ошибку при конфликте

  • :replace_all — заменяет все значения существующей строки значениями из схемы/changeset, включая поля, явно не заданные в changeset, например идентификаторы и автоматически сгенерированные временные метки (inserted_at и updated_at). Не используйте этот параметр, если у вас есть автоинкрементные первичные ключи: они также будут заменены. Скорее всего, вам стоит вместо этого явно использовать {:replace_all_except, [:id]} или {:replace, fields}. Для этого параметра требуется схема. Поля, указанные в :conflict_target, будут проигнорированы, если только :replace_changed не настроен как false

  • {:replace_all_except, fields} — то же, что и выше, но указанные поля (и поля, заданные в качестве цели конфликта) не заменяются. Для этого параметра требуется схема

  • {:replace, fields} — заменяет только определённые столбцы. Для этого параметра требуется :conflict_target. Как правило, следует убедиться, что заданные для замены поля не пересекаются с conflict_target, поскольку тогда базы данных смогут выполнять upsert эффективнее

  • список ключевых слов с инструкциями обновления — например, тот, который передаётся в update_all/3, например: [set: [title: "new title"]]

  • запрос Ecto.Query, который будет использоваться как инструкция UPDATE, например, передаваемая в update_all/3

В таких базах данных, как Postgres, upsert преобразуется в "ON CONFLICT", а в таких, как MySQL, — в "ON DUPLICATE KEY".

Возвращаемые значения

По умолчанию Postgres и MySQL возвращают количество записей, вставленных с помощью insert_all/3. Однако при указании параметра :on_conflict Postgres и MySQL возвращают разные результаты.

Postgres учитывает только затронутые строки и возвращает 0, если новая запись не была добавлена.

MySQL возвращает как минимум количество записей, для которых была предпринята попытка вставки. Например, если :on_conflict задан как :nothing, MySQL вернёт количество записей, для которых была предпринята попытка вставки, даже если ни одна запись не была добавлена.

Также обратите внимание: если :on_conflict — это запрос, MySQL вернёт количество записей, для которых была предпринята попытка вставки, плюс количество записей, изменённых запросом UPDATE.

Заполнители

Передача карты в :placeholders позволяет отправлять меньше данных по сети, если во многих записях значение одного и того же поля совпадает. Чтобы использовать заполнитель, замените значение в каждой записи на {:placeholder, key}, где key — это ключ, используемый в карте параметра :placeholders. Например:

placeholders = %{blob: large_blob_of_text(...)}

entries = [
  %{title: "v1", body: {:placeholder, :blob}},
  %{title: "v2", body: {:placeholder, :blob}}
]

Repo.insert_all(Post, entries, placeholders: placeholders)

Обратите внимание:

  • заполнители нельзя вкладывать в другие значения. Например, нельзя поместить заполнитель в массив. Вместо этого весь массив должен быть заполнителем

  • ключ заполнителя можно использовать только со столбцами одного типа

  • для заполнителей требуется база данных с поддержкой индексных параметров, поэтому в настоящее время они несовместимы с MySQL

insert_or_update(changeset, opts)Исходный код

@callback insert_or_update(changeset :: Ecto.Changeset.t(), opts :: Keyword.t()) ::
  {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Вставляет или обновляет changeset в зависимости от того, сохранена ли структура в базе данных.

Решение о вставке или обновлении принимается на основании поля Ecto.Schema.Metadata — :state. Ecto автоматически задаёт :state при загрузке или создании схемы.

Обратите внимание: для работы этой функции необходимо загрузить существующие структуры из базы данных. Поэтому следующий вариант не сработает, даже если структура существует:

struct = %Post{id: "existing_id", ...}
MyRepo.insert_or_update(changeset)
# => {:error, changeset} # id already exists

Параметры

  • :prefix — префикс, в котором выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Переопределяет префикс, заданный в запросе, и любой @schema_prefix, заданный в схемах. Кроме того, @schema_prefix родительской записи переопределяет все значения @schema_prefix по умолчанию, заданные в дочерних схемах связей.
  • :stale_error_field — поле, в которое будут добавлены ошибки устаревших данных в возвращаемом changeset. Этот параметр позволяет избежать вызова Ecto.StaleEntryError. Применяется только при обновлении.
  • :stale_error_message — сообщение, добавляемое в настроенный :stale_error_field при возникновении ошибок устаревших данных; по умолчанию — "is stale". Применяется только при обновлении.
  • :allow_stale — не вызывает ошибку, если структуры устарели. По умолчанию — false. Параметр распространяется на связи.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

result =
  case MyRepo.get(Post, id) do
    nil  -> %Post{id: id} # Post not found, we build one
    post -> post          # Post exists, let's use it
  end
  |> Post.changeset(changes)
  |> MyRepo.insert_or_update()

case result do
  {:ok, struct}       -> # Inserted or updated with success
  {:error, changeset} -> # Something went wrong
end

insert_or_update!(changeset, opts)Исходный код

@callback insert_or_update!(changeset :: Ecto.Changeset.t(), opts :: Keyword.t()) ::
  Ecto.Schema.t()

То же, что и insert_or_update/2, но возвращает структуру или вызывает исключение, если changeset недействителен.

load(schema_or_map, data)Исходный код

@callback load(
  schema_or_map :: module() | map(),
  data :: map() | Keyword.t() | {list(), list()}
) :: Ecto.Schema.t() | map()

Загружает data в схему или карту.

Первым аргументом может быть модуль схемы или карта (типов). Первый аргумент определяет возвращаемое значение: соответственно структуру или карту.

Второй аргумент data указывает поля и значения, которые нужно загрузить. Это может быть карта, список ключевых слов или кортеж {fields, values}. Поля могут быть атомами или строками.

Поля, отсутствующие в схеме (или карте types), игнорируются. Если значение имеет недопустимый тип, вызывается ошибка.

Для загрузки данных из источников, отличных от базы данных, используйте Ecto.embedded_load/3.

Примеры

iex> MyRepo.load(User, %{name: "Alice", age: 25})
%User{name: "Alice", age: 25}

iex> MyRepo.load(User, [name: "Alice", age: 25])
%User{name: "Alice", age: 25}

data также может иметь вид {fields, values}:

iex> MyRepo.load(User, {[:name, :age], ["Alice", 25]})
%User{name: "Alice", age: 25, ...}

Первым аргументом также может быть карта types:

iex> types = %{name: :string, age: :integer}
iex> MyRepo.load(types, %{name: "Alice", age: 25})
%{name: "Alice", age: 25}

Эта функция особенно полезна при разборе результатов необработанных запросов:

iex> result = Ecto.Adapters.SQL.query!(MyRepo, "SELECT * FROM users", [])
iex> Enum.map(result.rows, &MyRepo.load(User, {result.columns, &1}))
[%User{...}, ...]

preload(structs_or_struct_or_nil, preloads, opts)Исходный код

@callback preload(structs_or_struct_or_nil, preloads :: term(), opts :: Keyword.t()) ::
  structs_or_struct_or_nil
when structs_or_struct_or_nil: [Ecto.Schema.t()] | Ecto.Schema.t() | nil

Предварительно загружает все связи для указанной структуры или структур.

Это похоже на Ecto.Query.preload/3, но позволяет предварительно загружать структуры после их получения из базы данных.

Если связь уже загружена, preload не будет пытаться загрузить её повторно. Предполагается, что для каждой связи уже загружен один и тот же набор вложенных связей. Если это не так, возможна потеря данных. Например:

comment1 = TestRepo.preload(comment1, [author: [:permalink]])
TestRepo.preload([comment1, comment2], :author)

Если оба комментария связаны с одним автором, у первого комментария будет удалена вложенная связь :permalink, поскольку для второго комментария она не была предварительно загружена. Чтобы избежать этого, необходимо также предварительно загрузить вложенные связи.

Чтобы сбросить загруженные поля, см. Ecto.reset_fields/2.

Параметры

  • :force — по умолчанию Ecto не загружает повторно уже загруженные связи. Если задать этому параметру значение true, все существующие связи будут отброшены и загружены заново.
  • :in_parallel — определяет, следует ли выполнять предварительную загрузку параллельно. Это возможно, только если предварительно загружается более одной связи и репозиторий не находится в транзакции. По умолчанию — true.
  • :prefix — префикс, из которого выполняется предварительная загрузка. По умолчанию запросы используют тот же префикс, что и первая структура в заданной коллекции. Этот параметр позволяет изменить префикс.
  • :on_preloader_spawn — при параллельной предварительной загрузке эта функция будет вызвана в процессах, выполняющих загрузку. Это может быть полезно для передачи контекста трассировок.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Примеры

# Use a single atom to preload an association
posts = Repo.preload posts, :comments

# Use a list of atoms to preload multiple associations
posts = Repo.preload posts, [:comments, :authors]

# Use a keyword list to preload nested associations as well
posts = Repo.preload posts, [comments: [:replies, :likes], authors: []]

# You can mix atoms and keywords, but the atoms must come first
posts = Repo.preload posts, [:authors, comments: [:likes, replies: [:reactions]]]

# Use a keyword list to customize how associations are queried
posts = Repo.preload posts, [comments: from(c in Comment, order_by: c.published_at)]

# Use a two-element tuple for a custom query and nested association definition
query = from c in Comment, order_by: c.published_at
posts = Repo.preload posts, [comments: {query, [:replies, :likes]}]

# Use a function for custom preloading
posts = Repo.preload posts, [comments: fn post_ids -> fetch_comments_by_post_ids(post_ids) end]

Запрос, переданный в preload, также может выполнять предварительную загрузку собственных связей. Подробности см. в разделах «Запросы предварительной загрузки» и «Функции предварительной загрузки» документации Ecto.Query.preload/3.

reload(struct_or_structs, opts)Исходный код

@callback reload(
  struct_or_structs :: Ecto.Schema.t() | [Ecto.Schema.t()],
  opts :: Keyword.t()
) :: Ecto.Schema.t() | [Ecto.Schema.t() | nil] | nil

Повторно загружает указанную схему или список схем из базы данных.

При использовании со списками предполагается, что все структуры в списке принадлежат одной схеме. Порядок элементов сохраняется. Результаты, не найденные в базе данных, будут возвращены как nil.

Предварительно загруженные связи будут отброшены, и их потребуется загрузить заново.

Пример

MyRepo.reload(post)
%Post{}

MyRepo.reload([post1, post2])
[%Post{}, %Post{}]

MyRepo.reload([deleted_post, post1])
[nil, %Post{}]

reload!(struct_or_structs, opts)Исходный код

@callback reload!(struct_or_structs, opts :: Keyword.t()) :: struct_or_structs
when struct_or_structs: Ecto.Schema.t() | [Ecto.Schema.t()]

Похожа на reload/2, но вызывает исключение, если что-либо не найдено.

При использовании со списками порядок элементов сохраняется.

Пример

MyRepo.reload!(post)
%Post{}

MyRepo.reload!([post1, post2])
[%Post{}, %Post{}]

update(changeset, opts)Исходный код

@callback update(changeset :: Ecto.Changeset.t(), opts :: Keyword.t()) ::
  {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Обновляет changeset, используя его первичный ключ.

Необходимо передать changeset, поскольку это единственный механизм отслеживания изменений. В базу данных отправляются только поля, присутствующие в части changes changeset. Все остальные изменения схемы, выполненные в памяти, игнорируются. Если требуется выполнить несколько операций с базой данных, они автоматически объединяются в транзакцию.

Если у структуры нет первичного ключа, будет вызвана ошибка Ecto.NoPrimaryKeyFieldError.

Если структуру не удастся найти, будет вызвана ошибка Ecto.StaleEntryError.

Возвращает {:ok, struct}, если структура успешно обновлена, или {:error, changeset}, если произошла ошибка валидации или известная ошибка ограничения.

Параметры

  • :returning — определяет, какие поля нужно вернуть. Принимает список полей, которые нужно получить из базы данных. Если указано true, возвращаются все поля, в том числе помеченные как load_in_query: false. Если указано false, дополнительные поля не возвращаются. Все поля из read_after_writes будут включены в любом случае. Также принимает {:unsafe_fragment, fragment}, чтобы передать необработанное SQL-выражение напрямую в предложение RETURNING (оно не экранируется, используйте с осторожностью). Учтите, что значения, полученные из базы данных, перезаписывают переданные пользователем значения. Поля, не возвращённые базой данных, сохранят исходные значения, переданные пользователем. Не все базы данных поддерживают этот параметр.

  • :force — по умолчанию, если в changeset нет изменений, update/2 ничего не делает. Если установить этот параметр в true, callbacks обновления будут выполняться всегда, даже при отсутствии изменений (включая временные метки).

  • :prefix — префикс, для которого выполняется запрос (например, путь схемы в Postgres или база данных в MySQL). Он переопределяет префикс, заданный в запросе, а также любые значения @schema_prefix, заданные в схемах. Кроме того, @schema_prefix родительской записи переопределит все значения @schema_prefix по умолчанию, заданные в дочерних схемах для ассоциаций.

  • :stale_error_field — поле, в которое будут добавлены ошибки устаревшей записи в возвращаемом changeset. Этот параметр можно использовать, чтобы избежать вызова ошибки Ecto.StaleEntryError.

  • :stale_error_message — сообщение, добавляемое к настроенному :stale_error_field при возникновении ошибок устаревшей записи. По умолчанию — «is stale».

  • :allow_stale — не вызывает ошибку, если запись устарела. По умолчанию — false. Такое может произойти, если структура была удалена из базы данных до обновления или если правило либо триггер в базе данных отклоняет операцию обновления. Этот параметр распространяется на ассоциации.

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Пример

post = MyRepo.get!(Post, 42)
post = Ecto.Changeset.change(post, title: "New title")
case MyRepo.update(post) do
  {:ok, struct}       -> # Updated with success
  {:error, changeset} -> # Something went wrong
end

update!(changeset, opts)Исходный код

@callback update!(changeset :: Ecto.Changeset.t(), opts :: Keyword.t()) :: Ecto.Schema.t()

Работает так же, как update/2, но возвращает структуру или вызывает ошибку, если changeset недействителен.

API транзакций

Функции для работы с транзакциями и подключениями к базе данных.

checked_out?()Исходный код

@callback checked_out?() :: boolean()

Возвращает true, если подключение было получено из пула.

Это верно, если выполняется код внутри Ecto.Repo.checkout/2 или Ecto.Repo.transact/2.

Примеры

MyRepo.checked_out?()
#=> false

MyRepo.transact(fn ->
  MyRepo.checked_out?() #=> true
end)

MyRepo.checkout(fn ->
  MyRepo.checked_out?() #=> true
end)

checkout(function, opts)Исходный код

@callback checkout((-> result), opts :: Keyword.t()) :: result when result: var

Получает подключение на время выполнения функции.

Возвращает результат функции. Это полезно, если нужно последовательно выполнить несколько операций с репозиторием и избежать многократного получения подключения.

checkout/2 и transaction/2 можно комбинировать и вкладывать друг в друга несколько раз. Если checkout/2 вызывается внутри функции другого вызова checkout/2, функция просто выполняется без получения нового подключения.

Параметры

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

in_transaction?()Исходный код

@callback in_transaction?() :: boolean()

Возвращает true, если текущий процесс выполняется внутри транзакции.

Если в тестах используется Ecto.Adapters.SQL.Sandbox, учтите: хотя каждый тест выполняется внутри транзакции, in_transaction?/0 вернёт true только внутри транзакций, явно созданных с помощью transaction/2. Это сделано для того, чтобы тестовая среда имитировала среды разработки и промышленной эксплуатации.

Примеры

MyRepo.in_transaction?()
#=> false

MyRepo.transact(fn ->
  MyRepo.in_transaction?() #=> true
end)

rollback(value)Исходный код

@callback rollback(value :: any()) :: no_return()

Откатывает текущую транзакцию.

Транзакция вернёт значение, переданное в качестве {:error, value}.

Обратите внимание: вызов rollback прекращает выполнение кода в транзакции.

transact(fun, opts)Исходный код

@callback transact(fun :: (-> result), opts :: Keyword.t()) :: result
when result: {:ok, any()} | {:error, any()}
@callback transact(multi :: Ecto.Multi.t(), opts :: Keyword.t()) ::
  {:ok, map()} | Ecto.Multi.failure()

Выполняет переданную функцию или Ecto.Multi внутри транзакции.

Использование с функцией

transact/2 можно вызывать с функцией нулевой или единичной арности. Функция нулевой арности будет просто выполнена:

Repo.transact(fn ->
  alice = Repo.insert!(alice_changeset)
  bob = Repo.insert!(bob_changeset)
  {:ok, [alice, bob]}
end)

Функция единичной арности получит репозиторий транзакции в качестве первого аргумента:

Repo.transact(fn repo ->
  alice = repo.insert!(alice_changeset)
  bob = repo.insert!(bob_changeset)
  {:ok, [alice, bob]}
end)

Возвращаемое значение совпадает со значением переданной fun, которая должна быть {:ok, result} или {:error, reason}.

Если эта функция возвращает {:ok, result}, это означает, что транзакция успешно зафиксирована. Если же она возвращает {:error, reason}, транзакция отменена.

Эта функция часто используется с with/1:

Repo.transact(fn ->
  with {:ok, alice} <- Repo.insert(alice_changeset),
       {:ok, bob} <- Repo.insert(bob_changeset) do
    {:ok, [alice, bob]}
  end
end)

Если возникает исключение Elixir, транзакция откатывается, а исключение передаётся из функции транзакции вызывающему коду. Если исключения нет, транзакция фиксируется, когда функция возвращает {:ok, result}. Возврат {:error, result} приведёт к откату транзакции, и эта функция также вернёт {:error, result}. Транзакцию можно явно откатить, вызвав rollback/1: это немедленно завершит выполнение функции и вернёт значение, переданное в rollback в качестве {:error, value}.

Вложенные транзакции

Если transact/2 вызывается внутри другой транзакции, функция просто выполняется, без дополнительной обёртки вызова транзакции. Если во внутренней транзакции произошла ошибка и она была перехвачена либо внутренняя транзакция была откачена, вся внешняя транзакция прерывается, что гарантирует отсутствие фиксации изменений.

Ниже показано, как работают откаты во вложенных транзакциях:

{:error, :rollback} =
  Repo.transact(fn ->
    {:error, :posting_not_allowed} =
      Repo.transact(fn ->
        # This function call causes the following to happen:
        #
        #   * the transaction is rolled back in the database,
        #   * code execution is stopped within the current function,
        #   * and the value, passed to `rollback/1` is returned from
        #     `Repo.transaction/1` as the second element in the error
        #     tuple.
        #
        Repo.rollback(:posting_not_allowed)

        # `rollback/1` stops execution, so code here won't be run
      end)

    # The transaction here is now aborted and any further
    # operation will raise an exception.
  end)

Дополнительные примеры прерванных транзакций и способы их обработки см. в разделе «Прерванные транзакции».

На практике управление вложенными транзакциями может быстро усложниться. Как правило, вместо этого стоит объединять операции в одной транзакции с помощью обычного управления потоком выполнения и with/1 либо использовать описанный далее Ecto.Multi.

Использование с Ecto.Multi

transact/2 также принимает структуру Ecto.Multi в качестве первого аргумента. Ecto.Multi позволяет поэтапно составлять операции транзакции и управлять тем, что происходит в случае успеха или сбоя.

Если этой функции передана структура Ecto.Multi, будет начата транзакция и выполнены все операции; в случае успеха транзакция будет зафиксирована, а функция вернёт {:ok, changes}:

# With Ecto.Multi
Ecto.Multi.new()
|> Ecto.Multi.insert(:post, %Post{})
|> Repo.transact()

В случае ошибок транзакция будет отменена, а функция вернёт {:error, failed_operation, failed_value, changes_so_far}.

Подробнее см. документацию Ecto.Multi, где также приведены подробные примеры.

Прерванные транзакции

Если операция внутри транзакции завершается с ошибкой, транзакция прерывается в базе данных. Например, при попытке вставить запись, нарушающую ограничение уникальности, вставка завершается с ошибкой и транзакция прерывается. В таких случаях любая последующая операция внутри транзакции вызовет исключение.

Рассмотрим следующую транзакцию:

Repo.transact(fn repo ->
  case Repo.insert(changeset) do
    {:ok, post} ->
      Repo.insert(%Status{value: "success"})

    {:error, changeset} ->
      Repo.insert(%Status{value: "failure"})
  end
end)

Если changeset действителен, но операция вставки завершается с ошибкой из-за ограничения базы данных, последующая операция Repo.insert(%Status{value: "failure"}) вызовет исключение: база данных уже прервала транзакцию, поэтому эта операция недопустима. В Postgres исключение будет выглядеть так:

** (Postgrex.Error) ERROR 25P02 (in_failed_sql_transaction) current transaction is aborted, commands ignored until end of transaction block

Если changeset недействителен ещё до обращения к базе данных из-за ошибки валидации, запрос не отправляется в базу данных, возвращается кортеж :error, а операция Repo.insert(%Status{value: "failure"}) выполняется как обычно.

Есть два способа справиться с такими ситуациями:

Если вы не хотите менять семантику кода, можно также использовать функцию savepoints, передав параметр :mode, например: Repo.insert(changeset, mode: :savepoint). В случае исключения транзакция откатится до точки сохранения, что позволит избежать её прерывания.

Другой вариант — выполнить эту операцию вне транзакции:

result =
  Repo.transact(fn ->
    with {:ok, post} <- Repo.insert(changeset) do
      Repo.insert(%Status{value: "success"})
    end
  end)

case result do
  {:ok, _} ->
    :ok

  {:error, _changeset} ->
    Repo.insert!(%Status{value: "failure"})
end

Работа с процессами

Транзакция привязана к процессу. Отдельный процесс, запущенный внутри транзакции, не будет частью той же транзакции и будет использовать отдельное подключение.

При использовании Ecto.Adapters.SQL.Sandbox в тестах подключением может быть возможно поделиться между процессами, однако родительский процесс обычно удерживает подключение до завершения транзакции. Это может привести к взаимной блокировке, если дочерний процесс попытается использовать то же подключение. Подробнее см. документацию Ecto.Adapters.SQL.Sandbox.

Параметры

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

Примеры

Если транзакция завершилась успешно, возвращается {:ok, result}:

iex> Repo.transact(fn ->
...>   Repo.insert(changeset)
...> end)
{:ok, %User{}}

Если транзакция завершилась с ошибкой, возвращается {:error, reason}:

iex> Repo.transact(fn ->
...>   Repo.insert(changeset)
...> end)
{:error, #Ecto.Changeset<...>}

Транзакцию можно прервать, вернув {:error, reason}, вызвав rollback/1 или вызвав исключение из переданной fun:

iex> Repo.transact(fn ->
...>   Repo.insert!(%User{}) # will be rolled back
...>   {:error, :oops}
...> end)
{:error, :oops}

iex> Repo.transact(fn ->
...>   Repo.insert!(%User{}) # will be rolled back
...>   Repo.rollback(:oops)
...> end)
{:error, :oops}

iex> Repo.transact(fn ->
...>   Repo.insert!(%User{}) # will be rolled back
...>   raise "oops"
...> end)
** (RuntimeError) oops

transaction(fun_or_multi, opts)Исходный код

Этот callback устарел. Используйте Repo.transact/2.
@callback transaction(fun_or_multi :: fun() | Ecto.Multi.t(), opts :: Keyword.t()) ::
  {:ok, any()} | {:error, any()} | Ecto.Multi.failure()

Выполняет переданную функцию или Ecto.Multi внутри транзакции.

Устарел; вместо него используйте transact/2.

Использование с функцией

transaction/2 можно вызывать с функцией нулевой или единичной арности. Функция нулевой арности будет просто выполнена:

import Ecto.Changeset, only: [change: 2]

MyRepo.transaction(fn ->
  MyRepo.update!(change(alice, balance: alice.balance - 10))
  MyRepo.update!(change(bob, balance: bob.balance + 10))
end)

Функция единичной арности получит репозиторий транзакции в качестве первого аргумента:

MyRepo.transaction(fn repo ->
  repo.insert!(%Post{})
end)

Если возникает исключение Elixir, транзакция откатывается, а исключение передаётся из функции транзакции вызывающему коду. Если исключения нет, транзакция фиксируется после возврата функции. Транзакцию можно явно откатить, вызвав rollback/1: это немедленно завершит выполнение функции и вернёт значение, переданное в rollback в качестве {:error, value}.

Успешная транзакция возвращает значение, возвращённое функцией, в кортеже {:ok, value}.

Дополнительные сведения см. в разделе transact/2.

Использование с Ecto.Multi

transaction/2 также принимает структуру Ecto.Multi в качестве первого аргумента. Ecto.Multi позволяет поэтапно составлять операции транзакции и управлять тем, что происходит в случае успеха или сбоя.

Если этой функции передана структура Ecto.Multi, будет начата транзакция и выполнены все операции; в случае успеха транзакция будет зафиксирована, а функция вернёт {:ok, changes}:

# With Ecto.Multi
Ecto.Multi.new()
|> Ecto.Multi.insert(:post, %Post{})
|> MyRepo.transaction()

В случае ошибок транзакция будет отменена, а функция вернёт {:error, failed_operation, failed_value, changes_so_far}.

Подробнее см. документацию Ecto.Multi, где также приведены подробные примеры.

Параметры

Дополнительные параметры см. в разделе «Общие параметры» документации модуля.

API процессов

Функции для работы с процессами репозитория.

all_running()Исходный код

@spec all_running() :: [atom() | pid()]

Возвращает все запущенные репозитории Ecto.

Порядок элементов списка не определён. Список содержит атомы для именованных репозиториев Ecto или PID.

get_dynamic_repo()Исходный код

@callback get_dynamic_repo() :: atom() | pid()

Возвращает имя текущего репозитория в виде атома или его PID.

Подробнее см. put_dynamic_repo/1.

put_dynamic_repo(name_or_pid)Исходный код

@callback put_dynamic_repo(name_or_pid :: atom() | pid()) :: atom() | pid()

Задаёт динамический репозиторий для последующих взаимодействий.

Иногда требуется, чтобы один репозиторий Ecto взаимодействовал с несколькими экземплярами базы данных. По умолчанию при вызове MyApp.Repo.start_link/1 будет запущен репозиторий с именем MyApp.Repo. Чтобы запустить несколько репозиториев, можно задать каждому из них своё имя:

MyApp.Repo.start_link(name: :tenant_foo, hostname: "foo.example.com")
MyApp.Repo.start_link(name: :tenant_bar, hostname: "bar.example.com")

Также можно запускать репозитории без имён, явно задав имя nil:

MyApp.Repo.start_link(name: nil, hostname: "temp.example.com")

Однако после запуска репозитория напрямую взаимодействовать с ним нельзя, поскольку все операции в MyApp.Repo по умолчанию отправляются в репозиторий с именем MyApp.Repo. Репозиторий по умолчанию можно изменить во время компиляции:

use Ecto.Repo, default_dynamic_repo: :name_of_repo

Также его можно изменить в любой момент во время выполнения, вызвав put_dynamic_repo/1:

MyApp.Repo.put_dynamic_repo(:tenant_foo)

С этого момента все последующие запросы текущего процесса будут выполняться в :tenant_foo.

Глобальные имена репозиториев

Разрешение имён репозиториев действует глобально для всех модулей репозиториев. При использовании put_dynamic_repo/1 убедитесь, что обращаетесь к нужному репозиторию: по ошибке можно обратиться к репозиторию из другого модуля:

Repo.start_link(name: :primary)
AnalyticsRepo.start_link(name: :analytics)

# This works but may not be intended - queries will use AnalyticsRepo's connection
Repo.put_dynamic_repo(:analytics)
Repo.all(User)  # Executes against AnalyticsRepo's connection!

start_link(opts)Исходный код

@callback start_link(opts :: Keyword.t()) ::
  {:ok, pid()} | {:error, {:already_started, pid()}} | {:error, term()}

Запускает дерево супервизии Repo.

Возвращает {:error, {:already_started, pid}}, если репозиторий уже запущен, или {:error, term}, если произошла какая-либо другая ошибка.

Параметры

Общие для адаптеров параметры см. в конфигурации документации модуля. Параметры конфигурации конкретного адаптера см. в документации этого адаптера.

stop(timeout)Исходный код

@callback stop(timeout()) :: :ok

Останавливает репозиторий.

API конфигурации

__adapter__()Исходный код

@callback __adapter__() :: Ecto.Adapter.t()

Возвращает адаптер, связанный с репозиторием.

config()Исходный код

@callback config() :: Keyword.t()

Возвращает конфигурацию адаптера, сохранённую в среде :otp_app.

Если в репозитории реализован callback init/2, он будет вызван с первым аргументом :runtime. Параметры, переданные в start_link/1, не учитываются.

Пользовательские callbacks

default_options(operation)Исходный код

@callback default_options(operation) :: Keyword.t()
when operation:
       :all
       | :delete
       | :delete_all
       | :insert
       | :insert_all
       | :insert_or_update
       | :preload
       | :reload
       | :stream
       | :transaction
       | :update
       | :update_all

Пользовательский callback, который можно настроить и который вызывается для получения параметров операций по умолчанию.

Его можно использовать, чтобы задавать значения по умолчанию для каждой операции, имеющие более высокий приоритет, чем значения из конфигурации или параметры, переданные при запуске репозитория. Также с его помощью можно задавать параметры для конкретных запросов, например :prefix.

Этот callback вызывается в начале выполнения любой операции с репозиторием. Например, при выполнении запроса с предварительной загрузкой связей callback будет вызван один раз в начале, а возвращённые им параметры будут переданы всем последующим операциям.

init(context, config)Исходный код

@callback init(context :: :supervisor | :runtime, config :: Keyword.t()) ::
  {:ok, Keyword.t()} | :ignore

Callback, выполняемый при запуске репозитория или чтении конфигурации.

Этот callback сохранён для обратной совместимости. В современном Elixir большую часть настроек времени выполнения можно задать в config/runtime.exs.

Первый аргумент — контекст вызова callback. Если он вызван при запуске супервизора Repo, значение будет :supervisor. Если callback вызван для чтения конфигурации без фактического запуска процесса, значение будет :runtime.

Второй аргумент — конфигурация репозитория, сохранённая в окружении приложения. Callback должен вернуть {:ok, keyword} с обновлённым списком конфигурации или :ignore (только в случае :supervisor).

prepare_query(operation, query, opts)Исходный код

@callback prepare_query(operation, query :: Ecto.Query.t(), opts :: Keyword.t()) ::
  {Ecto.Query.t(), Keyword.t()}
when operation: :all | :update_all | :delete_all | :stream | :insert_all

Пользовательский callback, который можно настроить и который вызывается для операций, основанных на запросах.

Этот callback можно использовать для дополнительного изменения запроса и параметров до их преобразования и отправки в базу данных.

Callback вызывается для всех API запросов, включая функции stream. Он также вызывается для insert_all, если задан исходный запрос. Для остальных функций схемы он не вызывается.

Примеры

Допустим, нужно исключать из всех операций записи, которые были «мягко удалены» (для которых задан столбец deleted_at), кроме случаев, когда запрос выполняет администратор. Callback можно определить так:

@impl true
def prepare_query(_operation, query, opts) do
  if opts[:admin] do
    {query, opts}
  else
    query = from(x in query, where: is_nil(x.deleted_at))
    {query, opts}
  end
end

Затем выполните запрос:

Repo.all(query)              # only non-deleted records are returned
Repo.all(query, admin: true) # all records are returned

Callback будет вызываться для всех запросов, включая запросы, выполняемые через ассоциации и предварительную загрузку. Для каждого отдельного соединения внутри запроса он не вызывается.

prepare_transaction(fun_or_multi, opts)Исходный код

@callback prepare_transaction(fun_or_multi :: fun() | Ecto.Multi.t(), opts :: Keyword.t()) ::
  {fun_or_multi :: fun() | Ecto.Multi.t(), Keyword.t()}

Пользовательский callback, который можно настроить и который вызывается для операций с транзакциями.

Этот callback можно использовать для дополнительного изменения переданной Ecto Multi и параметров операции с транзакцией до их преобразования и отправки в базу данных.

Этот callback вызывается только для транзакций.

Примеры

Предположим, что нужно добавлять комментарий SQL к операторам фиксации, используя параметр commit_comment транзакций.

@impl true
def prepare_transaction(multi_or_fun, opts) do
  opts = Keyword.put_new_lazy(opts, :commit_comment, fn -> extract_comment(opts) end)
  {multi_or_fun, opts}
end

Callback будет вызываться для каждой операции с транзакцией и попытается извлечь соответствующий комментарий фиксации, который затем будет использоваться адаптерами, поддерживающими этот параметр.

Типы

t()Исходный код

@type t() :: module()

Пакет Hex Предпросмотр Hex Просмотреть llms.txt Скачать версию ePub

Создано с помощью ExDoc (v0.40.2) для языка программирования Elixir

© 2014 Chris McCord
Licensed under the MIT License.
https://ecto.hexdocs.pm/Ecto.Repo.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API