модуль 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 279 def add_transaction_record(record) current_transaction.add_record(record) end
Зарегистрировать запись в текущей транзакции, чтобы можно было вызвать её обратные вызовы after_commit и after_rollback.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 288 def begin_db_transaction() end
Начинает транзакцию (и отключает автоматическое подтверждение).
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 302 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 307 def commit_db_transaction() end
Подтверждает транзакцию (и включает автоматическое подтверждение).
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 321 def default_sequence_name(table, column) nil end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 166 def delete(arel, name = nil, binds = []) sql, binds = to_sql_and_binds(arel, binds) exec_delete(sql, name, binds) end
Выполняет оператор delete и возвращает количество затронутых строк.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 393 def empty_insert_statement_value "DEFAULT VALUES" end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 128 def exec_delete(sql, name = nil, binds = []) exec_query(sql, name, binds) end
Выполняет оператор delete sql в контексте этого соединения, используя binds в качестве заменителей связывания. name регистрируется вместе с выполненным оператором sql.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 120 def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) sql, binds = sql_for_insert(sql, pk, nil, sequence_name, binds) exec_query(sql, name, binds) end
Выполняет оператор insert sql в контексте этого соединения, используя binds в качестве заменителей связывания. name регистрируется вместе с выполненным оператором sql.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 113 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 140 def exec_update(sql, name = nil, binds = []) exec_query(sql, name, binds) end
Выполняет оператор update sql в контексте этого соединения, используя binds в качестве заменителей связывания. name регистрируется вместе с выполненным оператором sql.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 106 def execute(sql, name = nil) raise NotImplementedError end
Выполняет SQL-запрос в контексте этого соединения и возвращает необработанный результат от адаптера соединения. Примечание: в зависимости от вашего соединителя базы данных, результат, возвращаемый этим методом, может управляться вручную в памяти. Рассмотрите возможность использования обертки #exec_query вместо этого.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 152 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 335
def insert_fixture(fixture, table_name)
fixture = fixture.stringify_keys
columns = schema_cache.columns_hash(table_name)
binds = fixture.map do |name, value|
if column = columns[name]
type = lookup_cast_type_from_column(column)
Relation::QueryAttribute.new(name, value, type)
else
raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
end
end
table = Arel::Table.new(table_name)
values = binds.map do |bind|
value = with_yaml_fallback(bind.value_for_database)
[table[bind.name], value]
end
manager = Arel::InsertManager.new
manager.into(table)
manager.insert(values)
execute manager.to_sql, "Fixture Insert"
end Вставляет заданный фикстуру в таблицу. Переопределяется в адаптерах, которым требуется что-то большее, чем просто вставка (например, Oracle). Большинство адаптеров должны реализовывать `insert_fixtures`, который использует пакетную SQL-вставку. Мы сохраняем этот метод, чтобы обеспечить резервное копирование для таких баз данных, как sqlite, которые не поддерживают пакетные вставки.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 363
def insert_fixtures(fixtures, table_name)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
`insert_fixtures` is deprecated and will be removed in the next version of Rails.
Consider using `insert_fixtures_set` for performance improvement.
MSG
return if fixtures.empty?
execute(build_fixture_sql(fixtures, table_name), "Fixtures Insert")
end Вставляет набор фикстур в таблицу. Переопределяется в адаптерах, которым требуется что-то большее, чем просто вставка (например, Oracle).
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 373
def insert_fixtures_set(fixture_set, tables_to_delete = [])
fixture_inserts = fixture_set.map do |table_name, fixtures|
next if fixtures.empty?
build_fixture_sql(fixtures, table_name)
end.compact
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}".dup }
total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts))
disable_referential_integrity do
transaction(requires_new: true) do
total_sql.each do |sql|
execute sql, "Fixtures Load"
yield if block_given?
end
end
end
end # File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 326 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 311 def rollback_db_transaction exec_rollback_db_transaction end
Откатывает транзакцию (и включает автоматическое подтверждение). Это должно быть сделано, если блок транзакции вызывает исключение или возвращает false.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 317 def rollback_to_savepoint(name = nil) exec_rollback_to_savepoint(name) end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 403
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 limits как есть.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 46
def select_all(arel, name = nil, binds = [], preparable: nil)
arel = arel_from_relation(arel)
sql, binds = to_sql_and_binds(arel, binds)
if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
preparable = false
elsif binds.length > bind_params_length
sql, binds = unprepared_statement { to_sql_and_binds(arel) }
preparable = false
else
preparable = visitor.preparable
end
if prepared_statements && preparable
select_prepared(sql, name, binds)
else
select(sql, name, binds)
end
end Возвращает экземпляр ActiveRecord::Result.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 68 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 85 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 73 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 79 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 252
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 Выполняет указанный блок в транзакции базы данных и возвращает результат блока.
Поддержка вложенных транзакций
Большинство баз данных не поддерживают истинные вложенные транзакции. На момент написания, единственная база данных, о которой нам известно, которая поддерживает истинные вложенные транзакции, — это MS-SQL.
Для решения этой проблемы, transaction эмулирует эффект вложенных транзакций, используя сохраняемые точки: dev.mysql.com/doc/refman/5.7/en/savepoint.html Сохраняемые точки поддерживаются MySQL и PostgreSQL. SQLite3 версии >= '3.6.8' поддерживает сохраняемые точки.
Безопасно вызывать этот метод, если транзакция базы данных уже открыта, т.е. если transaction вызывается внутри другого блока transaction. В случае вложенного вызова transaction будет действовать следующим образом:
-
Блок будет выполнен без каких-либо действий. Все операторы базы данных, которые происходят внутри блока, фактически добавляются к уже открытой транзакции базы данных.
-
Однако, если
:requires_newзадано, блок будет обернут в сохраняемую точку базы данных, действующую как под-транзакция.
Ограничения
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 290
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 269 def transaction_open? current_transaction.open? end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 283 def transaction_state current_transaction.state end
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 133 def truncate(table_name, name = nil) raise NotImplementedError end
Выполняет оператор truncate.
# File activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb, line 160 def update(arel, name = nil, binds = []) sql, binds = to_sql_and_binds(arel, binds) exec_update(sql, name, binds) end
Выполняет оператор update и возвращает количество затронутых строк.
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.