Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Support for multi-table updates with limits, offsets and orders

Conflicts:

	activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  • Loading branch information...
commit e81e50888d33c0b17969f2fd932736ff1d64296d 1 parent 044fb8c
Jon Leighton jonleighton authored
3  activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
View
@@ -339,6 +339,9 @@ def sanitize_limit(limit)
def join_to_update(update, select) #:nodoc:
subselect = select.clone
subselect.ast.cores.last.projections = [update.ast.key]
+
+ update.ast.limit = nil
+ update.ast.orders = []
update.wheres = [update.ast.key.in(subselect)]
end
23 activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
View
@@ -599,8 +599,29 @@ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
where_sql
end
+ # 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!
def join_to_update(update, select) #:nodoc:
- update.table select.ast.cores.last.source
+ if select.limit || select.offset || select.orders.any?
+ subsubselect = select.ast.clone
+ subsubselect.cores.last.projections = [update.ast.key]
+ subsubselect = Arel::Nodes::TableAlias.new(
+ Arel::Nodes::Grouping.new(subsubselect),
+ '__active_record_temp'
+ )
+
+ subselect = Arel::SelectManager.new(select.engine, subsubselect)
+ subselect.project(Arel::Table.new('__active_record_temp')[update.ast.key.name])
+
+ update.ast.limit = nil
+ update.ast.orders = []
+ update.wheres = [update.ast.key.in(subselect)]
+ else
+ update.table select.ast.cores.last.source
+ end
end
protected
23 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
View
@@ -505,8 +505,29 @@ def add_limit_offset!(sql, options) #:nodoc:
end
deprecate :add_limit_offset!
+ # 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!
def join_to_update(update, select) #:nodoc:
- update.table select.ast.cores.last.source
+ if select.limit || select.offset || select.orders.any?
+ subsubselect = select.ast.clone
+ subsubselect.cores.last.projections = [update.ast.key]
+ subsubselect = Arel::Nodes::TableAlias.new(
+ Arel::Nodes::Grouping.new(subsubselect),
+ '__active_record_temp'
+ )
+
+ subselect = Arel::SelectManager.new(select.engine, subsubselect)
+ subselect.project(Arel::Table.new('__active_record_temp')[update.ast.key.name])
+
+ update.ast.limit = nil
+ update.ast.orders = []
+ update.wheres = [update.ast.key.in(subselect)]
+ else
+ update.table select.ast.cores.last.source
+ end
end
# SCHEMA STATEMENTS ========================================
30 activerecord/test/cases/relations_test.rb
View
@@ -960,4 +960,34 @@ def test_update_all_with_joins
assert_equal count, comments.update_all(:post_id => posts(:thinking).id)
assert_equal posts(:thinking), comments(:greetings).post
end
+
+ def test_update_all_with_joins_and_limit
+ comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).limit(1)
+ assert_equal 1, comments.update_all(:post_id => posts(:thinking).id)
+ end
+
+ def test_update_all_with_joins_and_limit_and_order
+ comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('comments.id').limit(1)
+ assert_equal 1, comments.update_all(:post_id => posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:greetings).post
+ assert_equal posts(:welcome), comments(:more_greetings).post
+ end
+
+ def test_update_all_with_joins_and_offset
+ all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id)
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id)
+ end
+
+ def test_update_all_with_joins_and_offset_and_order
+ all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('posts.id')
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:more_greetings).post
+ assert_equal posts(:welcome), comments(:greetings).post
+ end
end
Please sign in to comment.
Something went wrong with that request. Please try again.