Fix delete_all when chained with joins.

Closes #5202 and #919
This commit is contained in:
Rafael Mendonça França 2012-03-29 00:42:24 -03:00
parent 84adc21498
commit 6f6c2909dc
4 changed files with 59 additions and 17 deletions

@ -312,13 +312,27 @@ def sanitize_limit(limit)
# on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
# an UPDATE statement, so in the mysql adapters we redefine this to do that.
def join_to_update(update, select) #:nodoc:
subselect = select.clone
subselect.projections = [update.key]
key = update.key
subselect = subquery_for(key, select)
update.where update.key.in(subselect)
update.where key.in(subselect)
end
def join_to_delete(delete, select, key) #:nodoc:
subselect = subquery_for(key, select)
delete.where key.in(subselect)
end
protected
# Return a subquery for the given key using the join information.
def subquery_for(key, select)
subselect = select.clone
subselect.projections = [key]
subselect
end
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])

@ -294,19 +294,10 @@ def release_savepoint
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
# these, we must use a subquery. However, MySQL is too stupid to create a
# temporary table for this automatically, so we have to give it some prompting
# in the form of a subsubquery. Ugh!
# these, we must use a subquery.
def join_to_update(update, select) #:nodoc:
if select.limit || select.offset || select.orders.any?
subsubselect = select.clone
subsubselect.projections = [update.key]
subselect = Arel::SelectManager.new(select.engine)
subselect.project Arel.sql(update.key.name)
subselect.from subsubselect.as('__active_record_temp')
update.where update.key.in(subselect)
super
else
update.table select.source
update.wheres = select.constraints
@ -558,6 +549,17 @@ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
protected
# MySQL is too stupid to create a temporary table for use subquery, so we have
# to give it some prompting in the form of a subsubquery. Ugh!
def subquery_for(key, select)
subsubselect = select.clone
subsubselect.projections = [key]
subselect = Arel::SelectManager.new(select.engine)
subselect.project Arel.sql(key.name)
subselect.from subsubselect.as('__active_record_temp')
end
def add_index_length(option_strings, column_names, options = {})
if options.is_a?(Hash) && length = options[:length]
case length

@ -400,8 +400,16 @@ def delete_all(conditions = nil)
if conditions
where(conditions).delete_all
else
statement = arel.compile_delete
affected = @klass.connection.delete(statement, 'SQL', bind_values)
stmt = Arel::DeleteManager.new(arel.engine)
stmt.from(table)
if joins_values.any?
@klass.connection.join_to_delete(stmt, arel, table[primary_key])
else
stmt.wheres = arel.constraints
end
affected = @klass.connection.delete(stmt, 'SQL', bind_values)
reset
affected

@ -13,12 +13,14 @@
require 'models/parrot'
require 'models/minivan'
require 'models/person'
require 'models/pet'
require 'models/toy'
require 'rexml/document'
require 'active_support/core_ext/exception'
class PersistencesTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans, :pets, :toys
# Oracle UPDATE does not support ORDER BY
unless current_adapter?(:OracleAdapter)
@ -76,6 +78,22 @@ def test_delete_all
assert_equal Topic.count, Topic.delete_all
end
def test_delete_all_with_joins_and_where_part_is_hash
where_args = {:toys => {:name => 'Bone'}}
count = Pet.joins(:toys).where(where_args).count
assert_equal count, 1
assert_equal count, Pet.joins(:toys).where(where_args).delete_all
end
def test_delete_all_with_joins_and_where_part_is_not_hash
where_args = ['toys.name = ?', 'Bone']
count = Pet.joins(:toys).where(where_args).count
assert_equal count, 1
assert_equal count, Pet.joins(:toys).where(where_args).delete_all
end
def test_update_by_condition
Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
assert_equal "Have a nice day", Topic.find(1).content