модуль 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 349 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 354 def begin_db_transaction() end
Начинает транзакцию (и отключает автоматическую фиксацию).
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 368 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 373 def commit_db_transaction() end
Фиксирует транзакцию (и включает автоматическую фиксацию).
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 387 def default_sequence_name(table, column) nil end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 183 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 419 def empty_insert_statement_value(primary_key = nil) "DEFAULT VALUES" end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 142 def exec_delete(sql, name = nil, binds = []) exec_query(sql, name, binds) end
Выполняет оператор удаления sql в контексте этого соединения, используя binds в качестве подстановок связывания. name регистрируется вместе с выполненным оператором sql.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 134 def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) sql, binds = sql_for_insert(sql, pk, binds) exec_query(sql, name, binds) end
Выполняет оператор вставки sql в контексте этого соединения, используя binds в качестве подстановок связывания. name регистрируется вместе с выполненным оператором sql.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 127 def exec_query(sql, name = "SQL", binds = [], prepare: false) raise NotImplementedError end
Выполняет оператор sql в контексте этого соединения, используя binds в качестве подстановок связывания. name регистрируется вместе с выполненным оператором sql.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 149 def exec_update(sql, name = nil, binds = []) exec_query(sql, name, binds) end
Выполняет оператор обновления sql в контексте этого соединения, используя binds в качестве подстановок связывания. name регистрируется вместе с выполненным оператором sql.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 120 def execute(sql, name = nil) raise NotImplementedError end
Выполняет SQL-запрос в контексте этого соединения и возвращает необработанный результат от адаптера соединения. Примечание: в зависимости от вашего соединителя базы данных, результат, возвращаемый этим методом, может управляться вручную в памяти. Рассмотрите возможность использования обертки exec_query вместо этого.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 169 def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) sql, binds = to_sql_and_binds(arel, binds) value = exec_insert(sql, name, binds, pk, sequence_name) id_value || last_inserted_id(value) end
Выполняет запрос INSERT и возвращает ID новой записи
id_value будет возвращено, если значение не nil, в этом случае база данных попытается вычислить последний вставленный ID и вернуть это значение.
Если следующий ID был вычислен заранее (как в Oracle), его следует передать как id_value.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 401 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 405
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 392 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 377 def rollback_db_transaction exec_rollback_db_transaction end
Откатывает транзакцию (и включает автоматическую фиксацию). Это должно быть сделано, если блок транзакции вызывает исключение или возвращает false.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 383 def rollback_to_savepoint(name = nil) exec_rollback_to_savepoint(name) end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 429
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. Это должно выглядеть как целое число или литерал SQL Arel.
Возвращает пределы Integer и Arel::Nodes::SqlLiteral как есть.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 62
def select_all(arel, name = nil, binds = [], preparable: nil)
arel = arel_from_relation(arel)
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
if prepared_statements && preparable
select_prepared(sql, name, binds)
else
select(sql, name, binds)
end
rescue ::RangeError
ActiveRecord::Result.new([], [])
end Возвращает экземпляр ActiveRecord::Result.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 77 def select_one(arel, name = nil, binds = []) select_all(arel, name, binds).first end
Возвращает хеш записи с именами столбцов в качестве ключей и значениями столбцов.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 94 def select_rows(arel, name = nil, binds = []) select_all(arel, name, binds).rows end
Возвращает массив массивов, содержащих значения полей. Порядок такой же, как и возвращаемый columns.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 82 def select_value(arel, name = nil, binds = []) single_value_from_rows(select_rows(arel, name, binds)) end
Возвращает единственное значение из записи.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 88 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 313
def transaction(requires_new: nil, isolation: nil, joinable: true)
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) { yield }
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
Это создает как запись “first”, так и “second”. Причина в том, что исключение 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
будет создана только запись с заголовком “first”.
См. 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 и postgresql поддерживают установку уровня изоляции транзакции.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 356
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 339 def transaction_open? current_transaction.open? end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 189 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 177 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 111 def write_query?(sql) raise NotImplementedError end
Определяет, является ли SQL-запрос операцией записи.
© 2004–2020 David Heinemeier Hansson
Licensed under the MIT License.