Spec-Zone.ru › Ruby on Rails 6.1

модуль ActiveRecord::ConnectionAdapters::DatabaseStatements

Открытые методы класса

new() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 6
def initialize
  super
  reset_transaction
end
Вызывает метод суперкласса

Публичные методы экземпляра

add_transaction_record(record, ensure_finalize = true) Показать исходный код
# 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.

begin_db_transaction() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 354
def begin_db_transaction()    end

Начинает транзакцию (и отключает автоматическую фиксацию).

begin_isolated_db_transaction(isolation) Показать исходный код
# 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

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

commit_db_transaction() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 373
def commit_db_transaction()   end

Фиксирует транзакцию (и включает автоматическую фиксацию).

create(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
Псевдоним для: insert
default_sequence_name(table, column) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 387
def default_sequence_name(table, column)
  nil
end
delete(arel, name = nil, binds = []) Показать исходный код
# 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

Выполняет оператор удаления и возвращает количество затронутых строк.

empty_insert_statement_value(primary_key = nil) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 419
def empty_insert_statement_value(primary_key = nil)
  "DEFAULT VALUES"
end
exec_delete(sql, name = nil, binds = []) Показать исходный код
# 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.

exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) Показать исходный код
# 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.

exec_query(sql, name = "SQL", binds = [], prepare: false) Показать исходный код
# 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.

exec_update(sql, name = nil, binds = []) Показать исходный код
# 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.

execute(sql, name = nil) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 120
def execute(sql, name = nil)
  raise NotImplementedError
end

Выполняет SQL-запрос в контексте этого соединения и возвращает необработанный результат от адаптера соединения. Примечание: в зависимости от вашего соединителя базы данных, результат, возвращаемый этим методом, может управляться вручную в памяти. Рассмотрите возможность использования обертки exec_query вместо этого.

insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) Показать исходный код
# 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.

Также является псевдонимом для: create
insert_fixture(fixture, table_name) Показать исходный код
# 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, которые не поддерживают массовые вставки.

insert_fixtures_set(fixture_set, tables_to_delete = []) Показать исходный код
# 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
reset_sequence!(table, column, sequence = nil) Показать исходный код
# 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

Устанавливает последовательность в максимальное значение столбца таблицы.

rollback_db_transaction() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 377
def rollback_db_transaction
  exec_rollback_db_transaction
end

Откатывает транзакцию (и включает автоматическую фиксацию). Это должно быть сделано, если блок транзакции вызывает исключение или возвращает false.

rollback_to_savepoint(name = nil) Показать исходный код
# 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
sanitize_limit(limit) Показать исходный код
# 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 как есть.

select_all(arel, name = nil, binds = [], preparable: nil) Показать исходный код
# 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.

select_one(arel, name = nil, binds = []) Показать исходный код
# 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

Возвращает хеш записи с именами столбцов в качестве ключей и значениями столбцов.

select_rows(arel, name = nil, binds = []) Показать исходный код
# 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.

select_value(arel, name = nil, binds = []) Показать исходный код
# 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

Возвращает единственное значение из записи.

select_values(arel, name = nil, binds = []) Показать исходный код
# 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]
to_sql(arel_or_sql_string, binds = []) Показать исходный код
# 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.

transaction(requires_new: nil, isolation: nil, joinable: true) { || ... } Показать исходный код
# 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

Обратитесь к документации вашей базы данных, чтобы понять семантику этих различных уровней:

  • www.postgresql.org/docs/current/static/transaction-iso.html

  • dev.mysql.com/doc/refman/en/set-transaction.html

Будет поднято исключение ActiveRecord::TransactionIsolationError, если:

  • Адаптер не поддерживает установку уровня изоляции

  • Вы присоединяетесь к уже открытой транзакции

  • Вы создаёте вложенную (с контрольной точкой) транзакцию

Адаптеры mysql2 и postgresql поддерживают установку уровня изоляции транзакции.

transaction_isolation_levels() Показать исходный код
# 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
transaction_open?() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 339
def transaction_open?
  current_transaction.open?
end
truncate(table_name, name = nil) Показать исходный код
# 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.

update(arel, name = nil, binds = []) Показать исходный код
# 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 и возвращает количество изменённых строк.

write_query?(sql) Показать исходный код
# 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.

Spec-Zone.ru

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