aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters
diff options
context:
space:
mode:
authorJoshua Peek <josh@joshpeek.com>2009-01-22 15:13:47 -0600
committerJoshua Peek <josh@joshpeek.com>2009-01-22 15:13:47 -0600
commitcc0b5fa9930dcc60914e21b518b3c54109243cfa (patch)
tree3b5c65d8d0329388730542093314028630b0945a /activerecord/lib/active_record/connection_adapters
parente57cb2629ac4971a5dcb1cf8bb2f6d0509317928 (diff)
parentccda96093a3bf3fb360f7c6d61bbbf341b2ae034 (diff)
downloadrails-cc0b5fa9930dcc60914e21b518b3c54109243cfa.tar.gz
rails-cc0b5fa9930dcc60914e21b518b3c54109243cfa.tar.bz2
rails-cc0b5fa9930dcc60914e21b518b3c54109243cfa.zip
Merge branch 'master' into 3-0-unstable
Conflicts: ci/cruise_config.rb
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb110
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb48
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb4
7 files changed, 210 insertions, 93 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 189c6c7b5a..08601da00a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -53,36 +53,124 @@ module ActiveRecord
def delete(sql, name = nil)
delete_sql(sql, name)
end
+
+ # Checks whether there is currently no transaction active. This is done
+ # by querying the database driver, and does not use the transaction
+ # house-keeping information recorded by #increment_open_transactions and
+ # friends.
+ #
+ # Returns true if there is no transaction active, false if there is a
+ # transaction active, and nil if this information is unknown.
+ #
+ # Not all adapters supports transaction state introspection. Currently,
+ # only the PostgreSQL adapter supports this.
+ def outside_transaction?
+ nil
+ end
+
+ # Runs the given block in a database transaction, and returns the result
+ # of the block.
+ #
+ # == Nested transactions support
+ #
+ # Most databases don't support true nested transactions. At the time of
+ # writing, the only database that supports true nested transactions that
+ # we're aware of, is MS-SQL.
+ #
+ # In order to get around this problem, #transaction will emulate the effect
+ # of nested transactions, by using savepoints:
+ # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
+ # Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
+ #
+ # It is safe to call this method if a database transaction is already open,
+ # i.e. if #transaction is called within another #transaction block. In case
+ # of a nested call, #transaction will behave as follows:
+ #
+ # - The block will be run without doing anything. All database statements
+ # that happen within the block are effectively appended to the already
+ # open database transaction.
+ # - However, if +:requires_new+ is set, the block will be wrapped in a
+ # database savepoint acting as a sub-transaction.
+ #
+ # === Caveats
+ #
+ # MySQL doesn't support DDL transactions. If you perform a DDL operation,
+ # then any created savepoints will be automatically released. For example,
+ # if you've created a savepoint, then you execute a CREATE TABLE statement,
+ # then the savepoint that was created will be automatically released.
+ #
+ # This means that, on MySQL, you shouldn't execute DDL operations inside
+ # a #transaction call that you know might create a savepoint. Otherwise,
+ # #transaction will raise exceptions when it tries to release the
+ # already-automatically-released savepoints:
+ #
+ # 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
+ def transaction(options = {})
+ options.assert_valid_keys :requires_new, :joinable
+
+ last_transaction_joinable = @transaction_joinable
+ if options.has_key?(:joinable)
+ @transaction_joinable = options[:joinable]
+ else
+ @transaction_joinable = true
+ end
+ requires_new = options[:requires_new] || !last_transaction_joinable
- # Wrap a block in a transaction. Returns result of block.
- def transaction(start_db_transaction = true)
transaction_open = false
begin
if block_given?
- if start_db_transaction
- begin_db_transaction
+ if requires_new || open_transactions == 0
+ if open_transactions == 0
+ begin_db_transaction
+ elsif requires_new
+ create_savepoint
+ end
+ increment_open_transactions
transaction_open = true
end
yield
end
rescue Exception => database_transaction_rollback
- if transaction_open
+ if transaction_open && !outside_transaction?
transaction_open = false
- rollback_db_transaction
+ decrement_open_transactions
+ if open_transactions == 0
+ rollback_db_transaction
+ else
+ rollback_to_savepoint
+ end
end
- raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
+ raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
end
ensure
- if transaction_open
+ @transaction_joinable = last_transaction_joinable
+
+ if outside_transaction?
+ @open_transactions = 0
+ elsif transaction_open
+ decrement_open_transactions
begin
- commit_db_transaction
+ if open_transactions == 0
+ commit_db_transaction
+ else
+ release_savepoint
+ end
rescue Exception => database_transaction_rollback
- rollback_db_transaction
+ if open_transactions == 0
+ rollback_db_transaction
+ else
+ rollback_to_savepoint
+ end
raise
end
end
end
-
+
# Begins the transaction (and turns off auto-committing).
def begin_db_transaction() end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 950bd72101..00c71090f3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -14,12 +14,12 @@ module ActiveRecord
def dirties_query_cache(base, *method_names)
method_names.each do |method_name|
base.class_eval <<-end_code, __FILE__, __LINE__
- def #{method_name}_with_query_dirty(*args)
- clear_query_cache if @query_cache_enabled
- #{method_name}_without_query_dirty(*args)
- end
-
- alias_method_chain :#{method_name}, :query_dirty
+ def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
+ clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
+ #{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
+ end # end
+ #
+ alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty
end_code
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index fe9cbcf024..273f823e7f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -476,12 +476,12 @@ module ActiveRecord
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
class_eval <<-EOV
- def #{column_type}(*args)
- options = args.extract_options!
- column_names = args
-
- column_names.each { |name| column(name, '#{column_type}', options) }
- end
+ def #{column_type}(*args) # def string(*args)
+ options = args.extract_options! # options = args.extract_options!
+ column_names = args # column_names = args
+ #
+ column_names.each { |name| column(name, '#{column_type}', options) } # column_names.each { |name| column(name, 'string', options) }
+ end # end
EOV
end
@@ -676,24 +676,24 @@ module ActiveRecord
# t.string(:goat, :sheep)
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
class_eval <<-EOV
- def #{column_type}(*args)
- options = args.extract_options!
- column_names = args
-
- column_names.each do |name|
- column = ColumnDefinition.new(@base, name, '#{column_type}')
- if options[:limit]
- column.limit = options[:limit]
- elsif native['#{column_type}'.to_sym].is_a?(Hash)
- column.limit = native['#{column_type}'.to_sym][:limit]
- end
- column.precision = options[:precision]
- column.scale = options[:scale]
- column.default = options[:default]
- column.null = options[:null]
- @base.add_column(@table_name, name, column.sql_type, options)
- end
- end
+ def #{column_type}(*args) # def string(*args)
+ options = args.extract_options! # options = args.extract_options!
+ column_names = args # column_names = args
+ #
+ column_names.each do |name| # column_names.each do |name|
+ column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string')
+ if options[:limit] # if options[:limit]
+ column.limit = options[:limit] # column.limit = options[:limit]
+ elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash)
+ column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit]
+ end # end
+ column.precision = options[:precision] # column.precision = options[:precision]
+ column.scale = options[:scale] # column.scale = options[:scale]
+ column.default = options[:default] # column.default = options[:default]
+ column.null = options[:null] # column.null = options[:null]
+ @base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options)
+ end # end
+ end # end
EOV
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index bfafcfb3ab..a8cd9f033b 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -66,6 +66,12 @@ module ActiveRecord
def supports_ddl_transactions?
false
end
+
+ # Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite
+ # does not.
+ def supports_savepoints?
+ false
+ end
# Should primary key values be selected from their corresponding
# sequence before the insert statement? If true, next_sequence_value
@@ -160,6 +166,23 @@ module ActiveRecord
@open_transactions -= 1
end
+ def transaction_joinable=(joinable)
+ @transaction_joinable = joinable
+ end
+
+ def create_savepoint
+ end
+
+ def rollback_to_savepoint
+ end
+
+ def release_savepoint
+ end
+
+ def current_savepoint_name
+ "active_record_#{open_transactions}"
+ end
+
def log_info(sql, name, ms)
if @logger && @logger.debug?
name = '%s (%.1fms)' % [name || 'SQL', ms]
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 46d4b6c89c..b2345fd571 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -13,23 +13,25 @@ module MysqlCompat #:nodoc:
# C driver >= 2.7 returns null values in each_hash
if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
target.class_eval <<-'end_eval'
- def all_hashes
- rows = []
- each_hash { |row| rows << row }
- rows
- end
+ def all_hashes # def all_hashes
+ rows = [] # rows = []
+ each_hash { |row| rows << row } # each_hash { |row| rows << row }
+ rows # rows
+ end # end
end_eval
# adapters before 2.7 don't have a version constant
# and don't return null values in each_hash
else
target.class_eval <<-'end_eval'
- def all_hashes
- rows = []
- all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
- each_hash { |row| rows << all_fields.dup.update(row) }
- rows
- end
+ def all_hashes # def all_hashes
+ rows = [] # rows = []
+ all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f|
+ fields[f.name] = nil; fields # fields[f.name] = nil; fields
+ } # }
+ each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) }
+ rows # rows
+ end # end
end_eval
end
@@ -208,6 +210,10 @@ module ActiveRecord
def supports_migrations? #:nodoc:
true
end
+
+ def supports_savepoints? #:nodoc:
+ true
+ end
def native_database_types #:nodoc:
NATIVE_DATABASE_TYPES
@@ -347,6 +353,17 @@ module ActiveRecord
# Transactions aren't supported
end
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
def add_limit_offset!(sql, options) #:nodoc:
if limit = options[:limit]
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 60ec01b95e..913bb521ca 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -272,6 +272,10 @@ module ActiveRecord
def supports_ddl_transactions?
true
end
+
+ def supports_savepoints?
+ true
+ end
# Returns the configured supported identifier length supported by PostgreSQL,
# or report the default of 63 on PostgreSQL 7.x.
@@ -528,45 +532,26 @@ module ActiveRecord
def rollback_db_transaction
execute "ROLLBACK"
end
+
+ if defined?(PGconn::PQTRANS_IDLE)
+ # The ruby-pg driver supports inspecting the transaction status,
+ # while the ruby-postgres driver does not.
+ def outside_transaction?
+ @connection.transaction_status == PGconn::PQTRANS_IDLE
+ end
+ end
- # ruby-pg defines Ruby constants for transaction status,
- # ruby-postgres does not.
- PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0
-
- # Check whether a transaction is active.
- def transaction_active?
- @connection.transaction_status != PQTRANS_IDLE
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
end
- # Wrap a block in a transaction. Returns result of block.
- def transaction(start_db_transaction = true)
- transaction_open = false
- begin
- if block_given?
- if start_db_transaction
- begin_db_transaction
- transaction_open = true
- end
- yield
- end
- rescue Exception => database_transaction_rollback
- if transaction_open && transaction_active?
- transaction_open = false
- rollback_db_transaction
- end
- raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
- end
- ensure
- if transaction_open && transaction_active?
- begin
- commit_db_transaction
- rescue Exception => database_transaction_rollback
- rollback_db_transaction
- raise
- end
- end
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
end
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
# SCHEMA STATEMENTS ========================================
@@ -950,13 +935,13 @@ module ActiveRecord
# should know about this but can't detect it there, so deal with it here.
money_precision = (postgresql_version >= 80300) ? 19 : 10
PostgreSQLColumn.module_eval(<<-end_eval)
- def extract_precision(sql_type)
- if sql_type =~ /^money$/
- #{money_precision}
- else
- super
- end
- end
+ def extract_precision(sql_type) # def extract_precision(sql_type)
+ if sql_type =~ /^money$/ # if sql_type =~ /^money$/
+ #{money_precision} # 19
+ else # else
+ super # super
+ end # end
+ end # end
end_eval
configure_connection
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 84f8c0284e..9387cf8827 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -402,6 +402,10 @@ module ActiveRecord
end
def add_column(table_name, column_name, type, options = {}) #:nodoc:
+ if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
+ raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
+ end
+
alter_table(table_name) do |definition|
definition.column(column_name, type, options)
end