модуль ActiveRecord::ConnectionAdapters::DatabaseStatements
Публичные методы класса
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 6 def initialize super reset_transaction end
Публичные методы экземпляра
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 390 def add_transaction_record(record, ensure_finalize = true) current_transaction.add_record(record, ensure_finalize) end
Регистрирует запись в текущей транзакции, чтобы можно было вызвать её обратные вызовы after_commit и after_rollback.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 395 def begin_db_transaction() end
Начинает транзакцию (и отключает автоматическую фиксацию).
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 409 def begin_isolated_db_transaction(isolation) raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation" end
Начинает транзакцию с установленным уровнем изоляции. По умолчанию вызывает ошибку; адаптеры, которые поддерживают установку уровня изоляции, должны реализовать этот метод.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 414 def commit_db_transaction() end
Фиксирует транзакцию (и включает автоматическую фиксацию).
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 436 def default_sequence_name(table, column) nil end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 206 def delete(arel, name = nil, binds = []) sql, binds = to_sql_and_binds(arel, binds) exec_delete(sql, name, binds) end
Выполняет оператор удаления и возвращает количество затронутых строк.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 468 def empty_insert_statement_value(primary_key = nil) "DEFAULT VALUES" end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 159 def exec_delete(sql, name = nil, binds = []) internal_exec_query(sql, name, binds) end
Выполняет оператор удаления sql в контексте этого соединения, используя binds в качестве подстановок связывания. name регистрируется вместе с выполненным оператором sql.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 151 def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) sql, binds = sql_for_insert(sql, pk, binds, returning) internal_exec_query(sql, name, binds) end
Выполняет оператор вставки sql в контексте этого соединения, используя binds в качестве подстановок связывания. name регистрируется вместе с выполненным оператором sql. Некоторые адаптеры поддерживают аргумент ключевого слова «returning», который позволяет управлять результатом запроса: `nil` — это значение по умолчанию и поддерживает поведение по умолчанию. Если передаётся массив имён столбцов, результат будет содержать значения указанных столбцов из вставленной строки.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 141 def exec_query(sql, name = "SQL", binds = [], prepare: false) internal_exec_query(sql, name, binds, prepare: prepare) end
Выполняет оператор sql в контексте этого соединения, используя binds в качестве подстановок связывания. name регистрируется вместе с выполненным оператором sql.
Примечание: предполагается, что запрос имеет побочные эффекты, и кэш запросов будет очищен. Если запрос является доступным только для чтения, рассмотрите возможность использования select_all вместо этого.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 166 def exec_update(sql, name = nil, binds = []) internal_exec_query(sql, name, binds) end
Выполняет оператор обновления sql в контексте этого соединения, используя binds в качестве подстановок связывания. name регистрируется вместе с выполненным оператором sql.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 130 def execute(sql, name = nil, allow_retry: false) internal_execute(sql, name, allow_retry: allow_retry) end
Выполняет оператор SQL в контексте этого соединения и возвращает необработанный результат из адаптера соединения.
Установка allow_retry в true заставляет базу данных переподключиться и повторить попытку выполнения оператора SQL в случае исключения, связанного с соединением. Этот параметр следует включать только для известных идемпотентных запросов.
Примечание: предполагается, что запрос имеет побочные эффекты, и кэш запросов будет очищен. Если запрос является доступным только для чтения, рассмотрите возможность использования select_all вместо этого.
Примечание: в зависимости от вашего соединителя базы данных, результат, возвращаемый этим методом, может управляться вручную в памяти. Вместо этого рассмотрите возможность использования оболочки exec_query.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 506 def high_precision_current_timestamp HIGH_PRECISION_CURRENT_TIMESTAMP end
Возвращает Arel литерал SQL для CURRENT_TIMESTAMP для использования со столбцами даты/времени с произвольной точностью.
Адаптеры, поддерживающие datetime с точностью, должны переопределить это, чтобы обеспечить максимально возможную точность.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 189 def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil) sql, binds = to_sql_and_binds(arel, binds) value = exec_insert(sql, name, binds, pk, sequence_name, returning: returning) return returning_column_values(value) unless returning.nil? id_value || last_inserted_id(value) end
Выполняет запрос INSERT и возвращает ID новой записи
id_value будет возвращено, если значение не nil, в этом случае база данных попытается вычислить последний вставленный ID и вернуть это значение.
Если следующий ID был вычислен заранее (как в Oracle), его следует передать как id_value. Некоторые адаптеры поддерживают аргумент ключевого слова «returning», который позволяет определить возвращаемое значение метода: `nil` — это значение по умолчанию и поддерживает поведение по умолчанию. Если передаётся массив имён столбцов, возвращается массив, представляющий значения указанных столбцов из вставленной строки.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 450 def insert_fixture(fixture, table_name) execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert") end
Вставляет заданный фикстур в таблицу. Переопределяется в адаптерах, которые требуют чего-то большего, чем просто вставка (например, Oracle). Большинство адаптеров должны реализовать insert_fixtures_set, который использует массовую вставку SQL. Мы сохраняем этот метод, чтобы обеспечить резервное копирование для таких баз данных, как SQLite, которые не поддерживают массовые вставки.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 454
def insert_fixtures_set(fixture_set, tables_to_delete = [])
fixture_inserts = build_fixture_statements(fixture_set)
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
statements = table_deletes + fixture_inserts
with_multi_statements do
disable_referential_integrity do
transaction(requires_new: true) do
execute_batch(statements, "Fixtures Load")
end
end
end
end # File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 441 def reset_sequence!(table, column, sequence = nil) # Do nothing by default. Implement for PostgreSQL, Oracle, ... end
Устанавливает последовательность в максимальное значение столбца таблицы.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 426 def restart_db_transaction exec_restart_db_transaction end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 418 def rollback_db_transaction exec_rollback_db_transaction rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::ConnectionFailed # Connection's gone; that counts as a rollback end
Откатывает транзакцию (и включает автоматическое подтверждение). Необходимо выполнить, если блок транзакции вызывает исключение или возвращает false.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 432 def rollback_to_savepoint(name = nil) exec_rollback_to_savepoint(name) end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 478
def sanitize_limit(limit)
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
limit
else
Integer(limit)
end
end Очищает переданный параметр LIMIT для предотвращения SQL-инъекций.
Значение limit может быть любым, что может быть преобразовано в строку через to_s. Оно должно выглядеть как целое число или Arel SQL-литерал.
Возвращает Integer и Arel::Nodes::SqlLiteral limits как есть.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 67 def select_all(arel, name = nil, binds = [], preparable: nil, async: false) arel = arel_from_relation(arel) sql, binds, preparable = to_sql_and_binds(arel, binds, preparable) select(sql, name, binds, prepare: prepared_statements && preparable, async: async && FutureResult::SelectAll) rescue ::RangeError ActiveRecord::Result.empty(async: async) end
Возвращает экземпляр ActiveRecord::Result.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 78 def select_one(arel, name = nil, binds = [], async: false) select_all(arel, name, binds, async: async).then(&:first) end
Возвращает хеш записи с именами столбцов в качестве ключей и значениями столбцов.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 95 def select_rows(arel, name = nil, binds = [], async: false) select_all(arel, name, binds, async: async).then(&:rows) end
Возвращает массив массивов, содержащих значения полей. Порядок соответствует порядку, возвращаемому columns.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 83
def select_value(arel, name = nil, binds = [], async: false)
select_rows(arel, name, binds, async: async).then { |rows| single_value_from_rows(rows) }
end Возвращает единственное значение из записи.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 89 def select_values(arel, name = nil, binds = []) select_rows(arel, name, binds).map(&:first) end
Возвращает массив значений первого столбца в запросе:
select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 12 def to_sql(arel_or_sql_string, binds = []) sql, _ = to_sql_and_binds(arel_or_sql_string, binds) sql end
Преобразует AST arel в SQL.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 337
def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
if !requires_new && current_transaction.joinable?
if isolation
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
end
yield
else
transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
end
rescue ActiveRecord::Rollback
# rollbacks are silently swallowed
end Выполняет заданный блок в базе данных с транзакцией и возвращает результат выполнения блока.
Вложенная поддержка транзакций
transaction вызовы могут быть вложены. По умолчанию, все операторы базы данных в блоке вложенной транзакции становятся частью родительской транзакции. Например, следующее поведение может быть неожиданным:
ActiveRecord::Base.transaction do
Post.create(title: 'first')
ActiveRecord::Base.transaction do
Post.create(title: 'second')
raise ActiveRecord::Rollback
end
end
Это создает как «первый», так и «второй» посты. Причина в том, что исключение ActiveRecord::Rollback во вложенном блоке не генерирует ROLLBACK. Поскольку эти исключения обрабатываются в блоках транзакций, родительский блок не видит их, и транзакция действительно подтверждается.
Большинство баз данных не поддерживают истинные вложенные транзакции. На момент написания, единственная база данных, которая поддерживает истинные вложенные транзакции, о которой нам известно, это MS-SQL.
Для решения этой проблемы transaction эмулирует эффект вложенных транзакций с использованием сохраняемых точек: dev.mysql.com/doc/refman/en/savepoint.html.
Вызов этого метода безопасен, если транзакция базы данных уже открыта, т. е. если transaction вызывается внутри другого блока transaction. В случае вложенного вызова transaction будет работать следующим образом:
-
Блок будет выполнен без каких-либо действий. Все операторы базы данных, которые происходят внутри блока, фактически добавляются к уже открытой базе данных транзакции.
-
Однако, если
:requires_newустановлено, блок будет заключен в сохраняемую точку базы данных, выступающую в роли подтранзакции.
Для получения ROLLBACK для вложенной транзакции вы можете запросить реальную подтранзакцию, передав requires_new: true. Если что-то пойдёт не так, база данных откатится до начала подтранзакции, не откатывая родительскую транзакцию. Если добавить это к предыдущему примеру:
ActiveRecord::Base.transaction do
Post.create(title: 'first')
ActiveRecord::Base.transaction(requires_new: true) do
Post.create(title: 'second')
raise ActiveRecord::Rollback
end
end
будет создан только пост с заголовком «первый».
См. ActiveRecord::Transactions для получения дополнительной информации.
Ограничения
MySQL не поддерживает транзакции DDL. Если вы выполните операцию DDL, то все созданные сохраняемые точки будут автоматически освобождены. Например, если вы создали сохраняемую точку, затем выполнили оператор CREATE TABLE, сохраняемая точка будет автоматически освобождена.
Это означает, что в MySQL вы не должны выполнять DDL-операции внутри вызова transaction, который, как вы знаете, может создать сохраняемую точку. В противном случае transaction будет генерировать исключения при попытке освободить уже автоматически освобождённые сохраняемые точки:
Model.connection.transaction do # BEGIN
Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
Model.connection.create_table(...)
# active_record_1 now automatically released
end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
end Изоляция транзакций
Если ваша база данных поддерживает установку уровня изоляции для транзакции, вы можете установить его так:
Post.transaction(isolation: :serializable) do # ... end
Допустимые уровни изоляции:
-
:read_uncommitted -
:read_committed -
:repeatable_read -
:serializable
Вы должны обратиться к документации вашей базы данных, чтобы понять семантику этих разных уровней:
Исключение ActiveRecord::TransactionIsolationError будет вызвано, если:
-
Адаптер не поддерживает установку уровня изоляции
-
Вы присоединяетесь к уже открытой транзакции
-
Вы создаёте вложенную (сохраняемую точку) транзакцию
Адаптеры mysql2, trilogy и postgresql поддерживают настройку уровня изоляции транзакций.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 397
def transaction_isolation_levels
{
read_uncommitted: "READ UNCOMMITTED",
read_committed: "READ COMMITTED",
repeatable_read: "REPEATABLE READ",
serializable: "SERIALIZABLE"
}
end # File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 364 def transaction_open? current_transaction.open? end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 212 def truncate(table_name, name = nil) execute(build_truncate_statement(table_name), name) end
Выполняет оператор truncate.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 200 def update(arel, name = nil, binds = []) sql, binds = to_sql_and_binds(arel, binds) exec_update(sql, name, binds) end
Выполняет оператор update и возвращает количество изменённых строк.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 112 def write_query?(sql) raise NotImplementedError end
Определяет, является ли SQL-запрос запросом записи.
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.