aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorMatthew Draper <matthew@trebex.net>2018-08-23 19:40:04 +0930
committerGitHub <noreply@github.com>2018-08-23 19:40:04 +0930
commit123fe0c9ac559cb58a208c23a96669845f24dfb1 (patch)
tree5ea6d9893882d6eb48af809c54ec9f5c622dea83 /activerecord/lib
parent2a321bff402bd990558b8a303f7168b5c4bcfc1d (diff)
parent0ac81ee6ff3d1625fdbcc40b12c00cbff2208077 (diff)
downloadrails-123fe0c9ac559cb58a208c23a96669845f24dfb1.tar.gz
rails-123fe0c9ac559cb58a208c23a96669845f24dfb1.tar.bz2
rails-123fe0c9ac559cb58a208c23a96669845f24dfb1.zip
Merge pull request #32647 from eugeneius/lazy_transactions
Omit BEGIN/COMMIT statements for empty transactions
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb76
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb8
9 files changed, 103 insertions, 14 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 41553cfa83..1d36c3c8b1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -259,7 +259,9 @@ module ActiveRecord
attr_reader :transaction_manager #:nodoc:
- delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
+ delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction,
+ :commit_transaction, :rollback_transaction, :materialize_transactions,
+ :disable_lazy_transactions!, :enable_lazy_transactions!, to: :transaction_manager
def transaction_open?
current_transaction.open?
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index b59df2fff7..564b226b39 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -91,12 +91,14 @@ module ActiveRecord
end
class Transaction #:nodoc:
- attr_reader :connection, :state, :records, :savepoint_name
+ attr_reader :connection, :state, :records, :savepoint_name, :isolation_level
def initialize(connection, options, run_commit_callbacks: false)
@connection = connection
@state = TransactionState.new
@records = []
+ @isolation_level = options[:isolation]
+ @materialized = false
@joinable = options.fetch(:joinable, true)
@run_commit_callbacks = run_commit_callbacks
end
@@ -105,6 +107,14 @@ module ActiveRecord
records << record
end
+ def materialize!
+ @materialized = true
+ end
+
+ def materialized?
+ @materialized
+ end
+
def rollback_records
ite = records.uniq
while record = ite.shift
@@ -141,24 +151,30 @@ module ActiveRecord
end
class SavepointTransaction < Transaction
- def initialize(connection, savepoint_name, parent_transaction, options, *args)
- super(connection, options, *args)
+ def initialize(connection, savepoint_name, parent_transaction, *args)
+ super(connection, *args)
parent_transaction.state.add_child(@state)
- if options[:isolation]
+ if isolation_level
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
end
- connection.create_savepoint(@savepoint_name = savepoint_name)
+
+ @savepoint_name = savepoint_name
+ end
+
+ def materialize!
+ connection.create_savepoint(savepoint_name)
+ super
end
def rollback
- connection.rollback_to_savepoint(savepoint_name)
+ connection.rollback_to_savepoint(savepoint_name) if materialized?
@state.rollback!
end
def commit
- connection.release_savepoint(savepoint_name)
+ connection.release_savepoint(savepoint_name) if materialized?
@state.commit!
end
@@ -166,22 +182,23 @@ module ActiveRecord
end
class RealTransaction < Transaction
- def initialize(connection, options, *args)
- super
- if options[:isolation]
- connection.begin_isolated_db_transaction(options[:isolation])
+ def materialize!
+ if isolation_level
+ connection.begin_isolated_db_transaction(isolation_level)
else
connection.begin_db_transaction
end
+
+ super
end
def rollback
- connection.rollback_db_transaction
+ connection.rollback_db_transaction if materialized?
@state.full_rollback!
end
def commit
- connection.commit_db_transaction
+ connection.commit_db_transaction if materialized?
@state.full_commit!
end
end
@@ -190,6 +207,9 @@ module ActiveRecord
def initialize(connection)
@stack = []
@connection = connection
+ @has_unmaterialized_transactions = false
+ @materializing_transactions = false
+ @lazy_transactions_enabled = true
end
def begin_transaction(options = {})
@@ -203,11 +223,41 @@ module ActiveRecord
run_commit_callbacks: run_commit_callbacks)
end
+ transaction.materialize! unless @connection.supports_lazy_transactions? && lazy_transactions_enabled?
@stack.push(transaction)
+ @has_unmaterialized_transactions = true if @connection.supports_lazy_transactions?
transaction
end
end
+ def disable_lazy_transactions!
+ materialize_transactions
+ @lazy_transactions_enabled = false
+ end
+
+ def enable_lazy_transactions!
+ @lazy_transactions_enabled = true
+ end
+
+ def lazy_transactions_enabled?
+ @lazy_transactions_enabled
+ end
+
+ def materialize_transactions
+ return if @materializing_transactions
+ return unless @has_unmaterialized_transactions
+
+ @connection.lock.synchronize do
+ begin
+ @materializing_transactions = true
+ @stack.each { |t| t.materialize! unless t.materialized? }
+ ensure
+ @materializing_transactions = false
+ end
+ @has_unmaterialized_transactions = false
+ end
+ end
+
def commit_transaction
@connection.lock.synchronize do
transaction = @stack.last
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 66ef8f0db3..8999d3232a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -80,6 +80,8 @@ module ActiveRecord
attr_reader :schema_cache, :owner, :logger, :prepared_statements, :lock
alias :in_use? :owner
+ set_callback :checkin, :after, :enable_lazy_transactions!
+
def self.type_cast_config_to_integer(config)
if config.is_a?(Integer)
config
@@ -342,6 +344,10 @@ module ActiveRecord
false
end
+ def supports_lazy_transactions?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -453,6 +459,7 @@ module ActiveRecord
# This is useful for when you need to call a proprietary method such as
# PostgreSQL's lo_* methods.
def raw_connection
+ disable_lazy_transactions!
@connection
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 9ff76435a1..ad045f85ef 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -180,6 +180,8 @@ module ActiveRecord
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
+ materialize_transactions
+
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.query(sql)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
index d89eeb7f54..684c7042a7 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -29,6 +29,8 @@ module ActiveRecord
end
def exec_query(sql, name = "SQL", binds = [], prepare: false)
+ materialize_transactions
+
if without_prepared_statement?(binds)
execute_and_free(sql, name) do |result|
ActiveRecord::Result.new(result.fields, result.to_a) if result
@@ -41,6 +43,8 @@ module ActiveRecord
end
def exec_delete(sql, name = nil, binds = [])
+ materialize_transactions
+
if without_prepared_statement?(binds)
execute_and_free(sql, name) { @connection.affected_rows }
else
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 544d720428..92f15de219 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -58,6 +58,10 @@ module ActiveRecord
true
end
+ def supports_lazy_transactions?
+ true
+ end
+
# HELPER METHODS ===========================================
def each_hash(result) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 8db2a645af..6bd6b67165 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -58,6 +58,8 @@ module ActiveRecord
# Queries the database and returns the results in an Array-like object
def query(sql, name = nil) #:nodoc:
+ materialize_transactions
+
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
result_as_array @connection.async_exec(sql)
@@ -70,6 +72,8 @@ module ActiveRecord
# Note: the PG::Result object is manually memory managed; if you don't
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
def execute(sql, name = nil)
+ materialize_transactions
+
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.async_exec(sql)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 4802a87c6a..30e651ee63 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -326,6 +326,10 @@ module ActiveRecord
postgresql_version >= 90400
end
+ def supports_lazy_transactions?
+ true
+ end
+
def get_advisory_lock(lock_id) # :nodoc:
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
@@ -597,6 +601,8 @@ module ActiveRecord
end
def exec_no_cache(sql, name, binds)
+ materialize_transactions
+
type_casted_binds = type_casted_binds(binds)
log(sql, name, binds, type_casted_binds) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -606,6 +612,8 @@ module ActiveRecord
end
def exec_cache(sql, name, binds)
+ materialize_transactions
+
stmt_key = prepare_statement(sql)
type_casted_binds = type_casted_binds(binds)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index c61e94f159..efe454fa7f 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -186,6 +186,10 @@ module ActiveRecord
true
end
+ def supports_lazy_transactions?
+ true
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity # :nodoc:
@@ -212,6 +216,8 @@ module ActiveRecord
end
def exec_query(sql, name = nil, binds = [], prepare: false)
+ materialize_transactions
+
type_casted_binds = type_casted_binds(binds)
log(sql, name, binds, type_casted_binds) do
@@ -252,6 +258,8 @@ module ActiveRecord
end
def execute(sql, name = nil) #:nodoc:
+ materialize_transactions
+
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.execute(sql)