diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 5bad5bfcc216a22f5f71736165feaf734813347d..d8cff30b8816b3c710a0c253687e4a43c371a7b7 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -356,11 +356,12 @@ def update_all(updates) stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name)) end - if has_join_values? || offset_value + if has_join_values? @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key)) else stmt.key = arel_attribute(primary_key) stmt.take(arel.limit) + stmt.offset(arel.offset) stmt.order(*arel.orders) stmt.wheres = arel.constraints end @@ -484,11 +485,12 @@ def delete_all stmt = Arel::DeleteManager.new stmt.from(table) - if has_join_values? || offset_value + if has_join_values? @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key)) else stmt.key = arel_attribute(primary_key) stmt.take(arel.limit) + stmt.offset(arel.offset) stmt.order(*arel.orders) stmt.wheres = arel.constraints end diff --git a/activerecord/lib/arel/nodes/delete_statement.rb b/activerecord/lib/arel/nodes/delete_statement.rb index 5be42a084ab0c9d2e176ff545dfd0b6c7a2f14bd..a41997533522da76e4dae14770c8fb4732cf145d 100644 --- a/activerecord/lib/arel/nodes/delete_statement.rb +++ b/activerecord/lib/arel/nodes/delete_statement.rb @@ -3,7 +3,7 @@ module Arel # :nodoc: all module Nodes class DeleteStatement < Arel::Nodes::Node - attr_accessor :left, :right, :orders, :limit, :key + attr_accessor :left, :right, :orders, :limit, :offset, :key alias :relation :left alias :relation= :left= @@ -16,6 +16,7 @@ def initialize(relation = nil, wheres = []) @right = wheres @orders = [] @limit = nil + @offset = nil @key = nil end @@ -26,7 +27,7 @@ def initialize_copy(other) end def hash - [self.class, @left, @right, @orders, @limit, @key].hash + [self.class, @left, @right, @orders, @limit, @offset, @key].hash end def eql?(other) @@ -35,6 +36,7 @@ def eql?(other) self.right == other.right && self.orders == other.orders && self.limit == other.limit && + self.offset == other.offset && self.key == other.key end alias :== :eql? diff --git a/activerecord/lib/arel/nodes/update_statement.rb b/activerecord/lib/arel/nodes/update_statement.rb index 017a553c4cf5c257620c2b61b3bd3b9fb2a721d1..cfaa19e39287b617a60804aae1ccbed2dd1aef70 100644 --- a/activerecord/lib/arel/nodes/update_statement.rb +++ b/activerecord/lib/arel/nodes/update_statement.rb @@ -3,7 +3,7 @@ module Arel # :nodoc: all module Nodes class UpdateStatement < Arel::Nodes::Node - attr_accessor :relation, :wheres, :values, :orders, :limit, :key + attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key def initialize @relation = nil @@ -11,6 +11,7 @@ def initialize @values = [] @orders = [] @limit = nil + @offset = nil @key = nil end @@ -21,7 +22,7 @@ def initialize_copy(other) end def hash - [@relation, @wheres, @values, @orders, @limit, @key].hash + [@relation, @wheres, @values, @orders, @limit, @offset, @key].hash end def eql?(other) @@ -31,6 +32,7 @@ def eql?(other) self.values == other.values && self.orders == other.orders && self.limit == other.limit && + self.offset == other.offset && self.key == other.key end alias :== :eql? diff --git a/activerecord/lib/arel/tree_manager.rb b/activerecord/lib/arel/tree_manager.rb index 149c69ce7a2796a1f17795ef5aef1e1fb52e9382..0476399618ce67eedc5a7a0ddb6b72bb5e51665c 100644 --- a/activerecord/lib/arel/tree_manager.rb +++ b/activerecord/lib/arel/tree_manager.rb @@ -10,6 +10,11 @@ def take(limit) self end + def offset(offset) + @ast.offset = Nodes::Offset.new(Nodes.build_quoted(offset)) if offset + self + end + def order(*expr) @ast.orders = expr self diff --git a/activerecord/lib/arel/visitors/mysql.rb b/activerecord/lib/arel/visitors/mysql.rb index 0f7d5aa8037384b938c03f2d86770ecc15e41593..eb8a449079d63d9513e21dcfcfdeccfec50a649b 100644 --- a/activerecord/lib/arel/visitors/mysql.rb +++ b/activerecord/lib/arel/visitors/mysql.rb @@ -56,18 +56,6 @@ def visit_Arel_Nodes_SelectCore(o, collector) super end - def visit_Arel_Nodes_UpdateStatement(o, collector) - collector << "UPDATE " - collector = visit o.relation, collector - - unless o.values.empty? - collector << " SET " - collector = inject_join o.values, collector, ", " - end - - collect_where_for(o, collector) - end - def visit_Arel_Nodes_Concat(o, collector) collector << " CONCAT(" visit o.left, collector @@ -77,7 +65,23 @@ def visit_Arel_Nodes_Concat(o, collector) collector end + def build_subselect(key, o) + subselect = super + + # Materialize subquery by adding distinct + # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' + subselect.distinct unless subselect.limit || subselect.offset || subselect.orders.any? + + Nodes::SelectStatement.new.tap do |stmt| + core = stmt.cores.last + core.froms = Nodes::Grouping.new(subselect).as("__active_record_temp") + core.projections = [Arel.sql(quote_column_name(key.name))] + end + end + def collect_where_for(o, collector) + return super if o.offset + unless o.wheres.empty? collector << " WHERE " collector = inject_join o.wheres, collector, " AND " diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb index 575bfd6e3667b3c57f8868ae2021ceaa2ee1f7d4..0172204fc874e8a7bba40070dfa2232fde4fceee 100644 --- a/activerecord/lib/arel/visitors/to_sql.rb +++ b/activerecord/lib/arel/visitors/to_sql.rb @@ -88,6 +88,7 @@ def build_subselect(key, o) core.wheres = o.wheres core.projections = [key] stmt.limit = o.limit + stmt.offset = o.offset stmt.orders = o.orders stmt end @@ -800,7 +801,7 @@ def inject_join(list, collector, join_str) end def collect_where_for(o, collector) - if o.orders.empty? && o.limit.nil? + if o.orders.empty? && o.limit.nil? && o.offset.nil? wheres = o.wheres else wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]