aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md20
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb159
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb156
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb10
-rw-r--r--activerecord/lib/active_record/core.rb1
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb9
-rw-r--r--activerecord/lib/active_record/railties/console_sandbox.rb2
-rw-r--r--activerecord/lib/active_record/relation/batches.rb13
-rw-r--r--activerecord/lib/active_record/transactions.rb35
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb4
-rw-r--r--activerecord/test/cases/adapter_test.rb32
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb32
-rw-r--r--activerecord/test/cases/base_test.rb30
-rw-r--r--activerecord/test/cases/batches_test.rb11
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb7
-rw-r--r--activerecord/test/cases/transactions_test.rb37
-rw-r--r--activerecord/test/models/post.rb5
25 files changed, 403 insertions, 232 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 475c237009..8b72529aff 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,25 @@
## Rails 4.0.0 (unreleased) ##
+* Explain only normal CRUD sql (select / update / insert / delete).
+ Fix problem that explains unexplainable sql. Closes #7544 #6458.
+
+ *kennyj*
+
+* Fix `find_in_batches` when primary_key is set other than id.
+ You can now use this method with the primary key which is not integer-based.
+
+ Example:
+
+ class Post < ActiveRecord::Base
+ self.primary_key = :title
+ end
+
+ Post.find_in_batches(:start => 'My First Post') do |batch|
+ batch.each { |post| post.author.greeting }
+ end
+
+ *Toshiyuki Kawanishi*
+
* You can now override the generated accessor methods for stored attributes
and reuse the original behavior with `read_store_attribute` and `write_store_attribute`,
which are counterparts to `read_attribute` and `write_attribute`.
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index b15df4f308..424c1a5b79 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -373,7 +373,7 @@ module ActiveRecord
# replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
count_with = $2.to_s
- count_with = '*' if count_with.blank? || count_with =~ /,/
+ count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
"SELECT #{$1}COUNT(#{count_with}) FROM"
end
end
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 11e4d34de2..32e3c7f5d8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -3,8 +3,7 @@ module ActiveRecord
module DatabaseStatements
def initialize
super
- @_current_transaction_records = []
- @transaction_joinable = nil
+ reset_transaction
end
# Converts an arel AST to SQL
@@ -108,20 +107,6 @@ module ActiveRecord
exec_delete(to_sql(arel, binds), name, binds)
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
-
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
def supports_statement_cache?
@@ -173,76 +158,60 @@ module ActiveRecord
def transaction(options = {})
options.assert_valid_keys :requires_new, :joinable
- last_transaction_joinable = @transaction_joinable
- @transaction_joinable = options.fetch(:joinable, true)
- requires_new = options[:requires_new] || !last_transaction_joinable
- transaction_open = false
-
- begin
- 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
- @_current_transaction_records.push([])
- end
+ if !options[:requires_new] && current_transaction.joinable?
yield
- rescue Exception => database_transaction_rollback
- if transaction_open && !outside_transaction?
- transaction_open = false
- txn = decrement_open_transactions
- txn.aborted!
- if open_transactions == 0
- rollback_db_transaction
- rollback_transaction_records(true)
- else
- rollback_to_savepoint
- rollback_transaction_records(false)
- end
- end
- raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
+ else
+ within_new_transaction(options) { yield }
end
+ rescue ActiveRecord::Rollback
+ # rollbacks are silently swallowed
+ end
+
+ def within_new_transaction(options = {}) #:nodoc:
+ begin_transaction(options)
+ yield
+ rescue Exception => error
+ rollback_transaction
+ raise
ensure
- @transaction_joinable = last_transaction_joinable
-
- if outside_transaction?
- @current_transaction = nil
- elsif transaction_open
- txn = decrement_open_transactions
- txn.committed!
- begin
- if open_transactions == 0
- commit_db_transaction
- commit_transaction_records
- else
- release_savepoint
- save_point_records = @_current_transaction_records.pop
- unless save_point_records.blank?
- @_current_transaction_records.push([]) if @_current_transaction_records.empty?
- @_current_transaction_records.last.concat(save_point_records)
- end
- end
- rescue Exception
- if open_transactions == 0
- rollback_db_transaction
- rollback_transaction_records(true)
- else
- rollback_to_savepoint
- rollback_transaction_records(false)
- end
- raise
- end
+ begin
+ commit_transaction unless error
+ rescue Exception
+ rollback_transaction
+ raise
end
end
+ def current_transaction #:nodoc:
+ @transaction
+ end
+
+ def transaction_open?
+ @transaction.open?
+ end
+
+ def begin_transaction(options = {}) #:nodoc:
+ @transaction = @transaction.begin
+ @transaction.joinable = options.fetch(:joinable, true)
+ @transaction
+ end
+
+ def commit_transaction #:nodoc:
+ @transaction = @transaction.commit
+ end
+
+ def rollback_transaction #:nodoc:
+ @transaction = @transaction.rollback
+ end
+
+ def reset_transaction #:nodoc:
+ @transaction = ClosedTransaction.new(self)
+ end
+
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
# can be called.
def add_transaction_record(record)
- last_batch = @_current_transaction_records.last
- last_batch << record if last_batch
+ @transaction.add_record(record)
end
# Begins the transaction (and turns off auto-committing).
@@ -356,42 +325,6 @@ module ActiveRecord
update_sql(sql, name)
end
- # Send a rollback message to all records after they have been rolled back. If rollback
- # is false, only rollback records since the last save point.
- def rollback_transaction_records(rollback)
- if rollback
- records = @_current_transaction_records.flatten
- @_current_transaction_records.clear
- else
- records = @_current_transaction_records.pop
- end
-
- unless records.blank?
- records.uniq.each do |record|
- begin
- record.rolledback!(rollback)
- rescue => e
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
- end
- end
- end
- end
-
- # Send a commit message to all records after they have been committed.
- def commit_transaction_records
- records = @_current_transaction_records.flatten
- @_current_transaction_records.clear
- unless records.blank?
- records.uniq.each do |record|
- begin
- record.committed!
- rescue => e
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
- end
- end
- end
- end
-
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
[sql, binds]
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
new file mode 100644
index 0000000000..2117eae5cb
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -0,0 +1,156 @@
+module ActiveRecord
+ module ConnectionAdapters
+ class Transaction #:nodoc:
+ attr_reader :connection
+
+ def initialize(connection)
+ @connection = connection
+ end
+ end
+
+ class ClosedTransaction < Transaction #:nodoc:
+ def number
+ 0
+ end
+
+ def begin
+ RealTransaction.new(connection, self)
+ end
+
+ def closed?
+ true
+ end
+
+ def open?
+ false
+ end
+
+ def joinable?
+ false
+ end
+
+ # This is a noop when there are no open transactions
+ def add_record(record)
+ end
+ end
+
+ class OpenTransaction < Transaction #:nodoc:
+ attr_reader :parent, :records
+ attr_writer :joinable
+
+ def initialize(connection, parent)
+ super connection
+
+ @parent = parent
+ @records = []
+ @finishing = false
+ @joinable = true
+ end
+
+ # This state is necesarry so that we correctly handle stuff that might
+ # happen in a commit/rollback. But it's kinda distasteful. Maybe we can
+ # find a better way to structure it in the future.
+ def finishing?
+ @finishing
+ end
+
+ def joinable?
+ @joinable && !finishing?
+ end
+
+ def number
+ if finishing?
+ parent.number
+ else
+ parent.number + 1
+ end
+ end
+
+ def begin
+ if finishing?
+ parent.begin
+ else
+ SavepointTransaction.new(connection, self)
+ end
+ end
+
+ def rollback
+ @finishing = true
+ perform_rollback
+ parent
+ end
+
+ def commit
+ @finishing = true
+ perform_commit
+ parent
+ end
+
+ def add_record(record)
+ records << record
+ end
+
+ def rollback_records
+ records.uniq.each do |record|
+ begin
+ record.rolledback!(parent.closed?)
+ rescue => e
+ record.logger.error(e) if record.respond_to?(:logger) && record.logger
+ end
+ end
+ end
+
+ def commit_records
+ records.uniq.each do |record|
+ begin
+ record.committed!
+ rescue => e
+ record.logger.error(e) if record.respond_to?(:logger) && record.logger
+ end
+ end
+ end
+
+ def closed?
+ false
+ end
+
+ def open?
+ true
+ end
+ end
+
+ class RealTransaction < OpenTransaction #:nodoc:
+ def initialize(connection, parent)
+ super
+ connection.begin_db_transaction
+ end
+
+ def perform_rollback
+ connection.rollback_db_transaction
+ rollback_records
+ end
+
+ def perform_commit
+ connection.commit_db_transaction
+ commit_records
+ end
+ end
+
+ class SavepointTransaction < OpenTransaction #:nodoc:
+ def initialize(connection, parent)
+ super
+ connection.create_savepoint
+ end
+
+ def perform_rollback
+ connection.rollback_to_savepoint
+ rollback_records
+ end
+
+ def perform_commit
+ connection.release_savepoint
+ records.each { |r| parent.add_record(r) }
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d5c7caad2e..3a8fbcf93f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -5,6 +5,7 @@ require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/schema_cache'
require 'active_record/connection_adapters/abstract/schema_dumper'
require 'monitor'
+require 'active_support/deprecation'
module ActiveRecord
module ConnectionAdapters # :nodoc:
@@ -34,6 +35,12 @@ module ActiveRecord
autoload :QueryCache
end
+ autoload_at 'active_record/connection_adapters/abstract/transaction' do
+ autoload :ClosedTransaction
+ autoload :RealTransaction
+ autoload :SavepointTransaction
+ end
+
# Active Record supports multiple database systems. AbstractAdapter and
# related classes form the abstraction layer which makes this possible.
# An AbstractAdapter represents a connection to a database, and provides an
@@ -64,14 +71,11 @@ module ActiveRecord
def initialize(connection, logger = nil, pool = nil) #:nodoc:
super()
- @active = nil
@connection = connection
@in_use = false
@instrumenter = ActiveSupport::Notifications.instrumenter
@last_use = false
@logger = logger
- @open_transactions = 0
- @current_transaction = nil
@pool = pool
@query_cache = Hash.new { |h,sql| h[sql] = {} }
@query_cache_enabled = false
@@ -184,19 +188,21 @@ module ActiveRecord
# checking whether the database is actually capable of responding, i.e. whether
# the connection isn't stale.
def active?
- @active != false
end
# Disconnects from the database if already connected, and establishes a
- # new connection with the database.
+ # new connection with the database. Implementors should call super if they
+ # override the default implementation.
def reconnect!
- @active = true
+ clear_cache!
+ reset_transaction
end
# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
- @active = false
+ clear_cache!
+ reset_transaction
end
# Reset the state of this connection, directing the DBMS to clear
@@ -240,33 +246,20 @@ module ActiveRecord
end
def open_transactions
- count = 0
- txn = current_transaction
-
- while txn
- count += 1
- txn = txn.next
- end
-
- count
+ @transaction.number
end
- attr_reader :current_transaction
-
def increment_open_transactions
- @current_transaction = Transaction.new(current_transaction)
+ ActiveSupport::Deprecation.warn "#increment_open_transactions is deprecated and has no effect"
end
def decrement_open_transactions
- return unless current_transaction
-
- txn = current_transaction
- @current_transaction = txn.next
- txn
+ ActiveSupport::Deprecation.warn "#decrement_open_transactions is deprecated and has no effect"
end
def transaction_joinable=(joinable)
- @transaction_joinable = joinable
+ ActiveSupport::Deprecation.warn "#transaction_joinable= is deprecated. Please pass the :joinable option to #begin_transaction instead."
+ @transaction.joinable = joinable
end
def create_savepoint
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 8fc172f6e8..328d080687 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -74,6 +74,7 @@ module ActiveRecord
end
def reconnect!
+ super
disconnect!
connect
end
@@ -82,6 +83,7 @@ module ActiveRecord
# Disconnects from the database if already connected.
# Otherwise, this method does nothing.
def disconnect!
+ super
unless @connection.nil?
@connection.close
@connection = nil
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index bb63fddf9b..0b936bbf39 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -189,14 +189,15 @@ module ActiveRecord
end
def reconnect!
+ super
disconnect!
- clear_cache!
connect
end
# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
+ super
@connection.close rescue nil
end
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 eb3084e066..c8437c18cc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation'
+
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
@@ -214,6 +216,10 @@ module ActiveRecord
end
def outside_transaction?
+ ActiveSupport::Deprecation.warn(
+ "#outside_transaction? is deprecated. This method was only really used " \
+ "internally, but you can use #transaction_open? instead."
+ )
@connection.transaction_status == PGconn::PQTRANS_IDLE
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 60f01c297e..8a073bf878 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -111,7 +111,7 @@ module ActiveRecord
inddef = row[3]
oid = row[4]
- columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
+ columns = Hash[query(<<-SQL, "SCHEMA")]
SELECT a.attnum, a.attname
FROM pg_attribute a
WHERE a.attrelid = #{oid}
@@ -252,7 +252,7 @@ module ActiveRecord
if pk && sequence
quoted_sequence = quote_table_name(sequence)
- select_value <<-end_sql, 'Reset sequence'
+ select_value <<-end_sql, 'SCHEMA'
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
end_sql
end
@@ -262,7 +262,7 @@ module ActiveRecord
def pk_and_sequence_for(table) #:nodoc:
# First try looking for a sequence with a dependency on the
# given table's primary key.
- result = query(<<-end_sql, 'PK and serial sequence')[0]
+ result = query(<<-end_sql, 'SCHEMA')[0]
SELECT attr.attname, seq.relname
FROM pg_class seq,
pg_attribute attr,
@@ -283,7 +283,7 @@ module ActiveRecord
# If that fails, try parsing the primary key's default value.
# Support the 7.x and 8.0 nextval('foo'::text) as well as
# the 8.1+ nextval('foo'::regclass).
- result = query(<<-end_sql, 'PK and custom sequence')[0]
+ result = query(<<-end_sql, 'SCHEMA')[0]
SELECT attr.attname,
CASE
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 1a727f9aa6..761052a788 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -473,9 +473,8 @@ module ActiveRecord
# Close then reopen the connection.
def reconnect!
- clear_cache!
+ super
@connection.reset
- @open_transactions = 0
configure_connection
end
@@ -487,7 +486,7 @@ module ActiveRecord
# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
- clear_cache!
+ super
@connection.close rescue nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 4fe0013f0f..4a48812807 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -104,6 +104,8 @@ module ActiveRecord
def initialize(connection, logger, config)
super(connection, logger)
+
+ @active = nil
@statements = StatementPool.new(@connection,
config.fetch(:statement_limit) { 1000 })
@config = config
@@ -154,11 +156,15 @@ module ActiveRecord
true
end
+ def active?
+ @active != false
+ end
+
# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
super
- clear_cache!
+ @active = false
@connection.close rescue nil
end
@@ -397,7 +403,7 @@ module ActiveRecord
table_name,
row['name'],
row['unique'] != 0,
- exec_query("PRAGMA index_info('#{row['name']}')", "Columns for index #{row['name']} on #{table_name}").map { |col|
+ exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
col['name']
})
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index cf64985ddb..43ffca0227 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -387,7 +387,6 @@ module ActiveRecord
@marked_for_destruction = false
@new_record = true
@mass_assignment_options = nil
- @txn = nil
@_start_transaction_state = {}
end
end
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index d5ba343b4c..0f927496fb 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -18,8 +18,9 @@ module ActiveRecord
# On the other hand, we want to monitor the performance of our real database
# queries, not the performance of the access to the query cache.
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
+ EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)/i
def ignore_payload?(payload)
- payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name])
+ payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS
end
ActiveSupport::Notifications.subscribe("sql.active_record", new)
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index b1db5f6f9f..60fc653735 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -843,9 +843,7 @@ module ActiveRecord
end
@fixture_connections = enlist_fixture_connections
@fixture_connections.each do |connection|
- connection.increment_open_transactions
- connection.transaction_joinable = false
- connection.begin_db_transaction
+ connection.begin_transaction joinable: false
end
# Load fixtures for every test.
else
@@ -868,10 +866,7 @@ module ActiveRecord
# Rollback changes if a transaction is active.
if run_in_transaction?
@fixture_connections.each do |connection|
- if connection.open_transactions != 0
- connection.rollback_db_transaction
- connection.decrement_open_transactions
- end
+ connection.rollback_transaction if connection.transaction_open?
end
@fixture_connections.clear
end
diff --git a/activerecord/lib/active_record/railties/console_sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb
index 65a3d68619..90b462fad6 100644
--- a/activerecord/lib/active_record/railties/console_sandbox.rb
+++ b/activerecord/lib/active_record/railties/console_sandbox.rb
@@ -1,6 +1,4 @@
-ActiveRecord::Base.connection.increment_open_transactions
ActiveRecord::Base.connection.begin_db_transaction
at_exit do
ActiveRecord::Base.connection.rollback_db_transaction
- ActiveRecord::Base.connection.decrement_open_transactions
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 4d14506965..d32048cce1 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -36,12 +36,12 @@ module ActiveRecord
# want multiple workers dealing with the same processing queue. You can
# make worker 1 handle all the records between id 0 and 10,000 and
# worker 2 handle from 10,000 and beyond (by setting the +:start+
- # option on that worker).
+ # option on that worker). You can also use non-integer-based primary keys
+ # if start point is set.
#
# It's not possible to set the order. That is automatically set to
- # ascending on the primary key ("id ASC") to make the batch ordering
- # work. This also mean that this method only works with integer-based
- # primary keys. You can't set the limit either, that's used to control
+ # ascending on the primary key (e.g. "id ASC") to make the batch ordering
+ # work. You can't set the limit either, that's used to control
# the batch sizes.
#
# Person.where("age > 21").find_in_batches do |group|
@@ -62,7 +62,8 @@ module ActiveRecord
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
- start = options.delete(:start).to_i
+ start = options.delete(:start)
+ start ||= 0
batch_size = options.delete(:batch_size) || 1000
relation = relation.reorder(batch_order).limit(batch_size)
@@ -70,7 +71,7 @@ module ActiveRecord
while records.any?
records_size = records.size
- primary_key_offset = records.last.id
+ primary_key_offset = records.last.send(primary_key)
yield records
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index e008b32170..09318879d5 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -1,32 +1,6 @@
require 'thread'
module ActiveRecord
- class Transaction
- attr_reader :next
-
- def initialize(txn = nil)
- @next = txn
- @committed = false
- @aborted = false
- end
-
- def committed!
- @committed = true
- end
-
- def aborted!
- @aborted = true
- end
-
- def committed?
- @committed
- end
-
- def aborted?
- @aborted
- end
- end
-
# See ActiveRecord::Transactions::ClassMethods for documentation.
module Transactions
extend ActiveSupport::Concern
@@ -333,11 +307,11 @@ module ActiveRecord
def with_transaction_returning_status
status = nil
self.class.transaction do
- @txn = self.class.connection.current_transaction
add_to_transaction
begin
status = yield
rescue ActiveRecord::Rollback
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
status = nil
end
@@ -353,17 +327,20 @@ module ActiveRecord
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
@_start_transaction_state[:new_record] = @new_record
@_start_transaction_state[:destroyed] = @destroyed
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
end
# Clear the new record state and id of a record.
def clear_transaction_record_state #:nodoc:
- @_start_transaction_state.clear if @txn.committed?
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ @_start_transaction_state.clear if @_start_transaction_state[:level] < 1
end
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
def restore_transaction_record_state(force = false) #:nodoc:
unless @_start_transaction_state.empty?
- if @txn.aborted? || force
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ if @_start_transaction_state[:level] < 1 || force
restore_state = @_start_transaction_state
was_frozen = @attributes.frozen?
@attributes = @attributes.dup if was_frozen
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 1199be68eb..59324c4857 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -36,6 +36,10 @@ module ActiveRecord
def columns(table_name)
@columns[table_name]
end
+
+ def active?
+ true
+ end
end
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 852fc0e26e..93b01a3934 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -160,4 +160,36 @@ module ActiveRecord
end
end
end
+
+ class AdapterTestWithoutTransaction < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def setup
+ @klass = Class.new(ActiveRecord::Base)
+ @klass.establish_connection 'arunit'
+ @connection = @klass.connection
+ end
+
+ def teardown
+ @klass.remove_connection
+ end
+
+ test "transaction state is reset after a reconnect" do
+ skip "in-memory db doesn't allow reconnect" if in_memory_db?
+
+ @connection.begin_transaction
+ assert @connection.transaction_open?
+ @connection.reconnect!
+ assert !@connection.transaction_open?
+ end
+
+ test "transaction state is reset after a disconnect" do
+ skip "in-memory db doesn't allow disconnect" if in_memory_db?
+
+ @connection.begin_transaction
+ assert @connection.transaction_open?
+ @connection.disconnect!
+ assert !@connection.transaction_open?
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 04714f42e9..5edabb4790 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -46,10 +46,13 @@ class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase
end
end
-class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestCase
+class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase
class Invoice < ActiveRecord::Base
ActiveSupport::Deprecation.silence do
has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT DISTINCT line_items.amount from line_items"
+ has_many :custom_full_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.invoice_id, line_items.amount from line_items"
+ has_many :custom_star_line_items, :class_name => 'LineItem', :finder_sql => "SELECT * from line_items"
+ has_many :custom_qualified_star_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.* from line_items"
end
end
@@ -61,6 +64,33 @@ class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestC
assert_equal 1, invoice.custom_line_items.count
end
+
+ def test_should_count_results_with_multiple_fields
+ invoice = Invoice.new
+ invoice.custom_full_line_items << LineItem.new(:amount => 0)
+ invoice.custom_full_line_items << LineItem.new(:amount => 0)
+ invoice.save!
+
+ assert_equal 2, invoice.custom_full_line_items.count
+ end
+
+ def test_should_count_results_with_star
+ invoice = Invoice.new
+ invoice.custom_star_line_items << LineItem.new(:amount => 0)
+ invoice.custom_star_line_items << LineItem.new(:amount => 0)
+ invoice.save!
+
+ assert_equal 2, invoice.custom_star_line_items.count
+ end
+
+ def test_should_count_results_with_qualified_star
+ invoice = Invoice.new
+ invoice.custom_qualified_star_line_items << LineItem.new(:amount => 0)
+ invoice.custom_qualified_star_line_items << LineItem.new(:amount => 0)
+ invoice.save!
+
+ assert_equal 2, invoice.custom_qualified_star_line_items.count
+ end
end
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index b9d480d9ce..b25d169038 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1619,26 +1619,32 @@ class BasicsTest < ActiveRecord::TestCase
def test_silence_sets_log_level_to_error_in_block
original_logger = ActiveRecord::Base.logger
- log = StringIO.new
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
- ActiveRecord::Base.logger.level = Logger::DEBUG
- ActiveRecord::Base.silence do
- ActiveRecord::Base.logger.warn "warn"
- ActiveRecord::Base.logger.error "error"
+
+ assert_deprecated do
+ log = StringIO.new
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
+ ActiveRecord::Base.logger.level = Logger::DEBUG
+ ActiveRecord::Base.silence do
+ ActiveRecord::Base.logger.warn "warn"
+ ActiveRecord::Base.logger.error "error"
+ end
+ assert_equal "error\n", log.string
end
- assert_equal "error\n", log.string
ensure
ActiveRecord::Base.logger = original_logger
end
def test_silence_sets_log_level_back_to_level_before_yield
original_logger = ActiveRecord::Base.logger
- log = StringIO.new
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
- ActiveRecord::Base.logger.level = Logger::WARN
- ActiveRecord::Base.silence do
+
+ assert_deprecated do
+ log = StringIO.new
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
+ ActiveRecord::Base.logger.level = Logger::WARN
+ ActiveRecord::Base.silence do
+ end
+ assert_equal Logger::WARN, ActiveRecord::Base.logger.level
end
- assert_equal Logger::WARN, ActiveRecord::Base.logger.level
ensure
ActiveRecord::Base.logger = original_logger
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index cdd4b49042..3b4ff83725 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -124,4 +124,15 @@ class EachTest < ActiveRecord::TestCase
assert_equal special_posts_ids, posts.map(&:id)
end
+ def test_find_in_batches_should_use_any_column_as_primary_key
+ title_order_posts = Post.order('title asc')
+ start_title = title_order_posts.first.title
+
+ posts = []
+ PostWithTitlePrimaryKey.find_in_batches(:batch_size => 1, :start => start_title) do |batch|
+ posts.concat(batch)
+ end
+
+ assert_equal title_order_posts.map(&:id), posts.map(&:id)
+ end
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index 91e1df91cd..b425967678 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -38,6 +38,13 @@ if ActiveRecord::Base.connection.supports_explain?
end
end
+ def test_collects_nothing_if_unexplained_sqls
+ with_queries([]) do |queries|
+ SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => 'SHOW max_identifier_length')
+ assert queries.empty?
+ end
+ end
+
def with_queries(queries)
Thread.current[:available_queries_for_explain] = queries
yield queries
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 0d0de455b3..0b5fda3817 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -36,6 +36,7 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ # FIXME: Get rid of this fucking global variable!
def test_successful_with_return
class << Topic.connection
alias :real_commit_db_transaction :commit_db_transaction
@@ -348,7 +349,6 @@ class TransactionTest < ActiveRecord::TestCase
def test_rollback_when_commit_raises
Topic.connection.expects(:begin_db_transaction)
Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
- Topic.connection.expects(:outside_transaction?).returns(false)
Topic.connection.expects(:rollback_db_transaction)
assert_raise RuntimeError do
@@ -397,31 +397,11 @@ class TransactionTest < ActiveRecord::TestCase
if current_adapter?(:PostgreSQLAdapter) && defined?(PGconn::PQTRANS_IDLE)
def test_outside_transaction_works
- assert Topic.connection.outside_transaction?
+ assert assert_deprecated { Topic.connection.outside_transaction? }
Topic.connection.begin_db_transaction
- assert !Topic.connection.outside_transaction?
+ assert assert_deprecated { !Topic.connection.outside_transaction? }
Topic.connection.rollback_db_transaction
- assert Topic.connection.outside_transaction?
- end
-
- def test_rollback_wont_be_executed_if_no_transaction_active
- assert_raise RuntimeError do
- Topic.transaction do
- Topic.connection.rollback_db_transaction
- Topic.connection.expects(:rollback_db_transaction).never
- raise "Rails doesn't scale!"
- end
- end
- end
-
- def test_open_transactions_count_is_reset_to_zero_if_no_transaction_active
- Topic.transaction do
- Topic.transaction do
- Topic.connection.rollback_db_transaction
- end
- assert_equal 0, Topic.connection.open_transactions
- end
- assert_equal 0, Topic.connection.open_transactions
+ assert assert_deprecated { Topic.connection.outside_transaction? }
end
end
@@ -580,5 +560,14 @@ if current_adapter?(:PostgreSQLAdapter)
assert_equal original_salary, Developer.find(1).salary
end
+
+ test "#transaction_joinable= is deprecated" do
+ Developer.transaction do
+ conn = Developer.connection
+ assert conn.current_transaction.joinable?
+ assert_deprecated { conn.transaction_joinable = false }
+ assert !conn.current_transaction.joinable?
+ end
+ end
end
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index c995f59a15..9858f68c4a 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -186,3 +186,8 @@ class SpecialPostWithDefaultScope < ActiveRecord::Base
self.table_name = 'posts'
default_scope { where(:id => [1, 5,6]) }
end
+
+class PostWithTitlePrimaryKey < ActiveRecord::Base
+ self.table_name = 'posts'
+ self.primary_key = :title
+end