From e5190acacd1088211cfe6f128b782af216aa6570 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 28 Sep 2018 03:38:55 +0900 Subject: [PATCH] Make `update_counters` preparable Before: ``` Topic Update All (0.4ms) UPDATE `topics` SET `topics`.`replies_count` = COALESCE(`topics`.`replies_count`, 0) + 1, `topics`.`updated_at` = '2018-09-27 18:34:05.068774' WHERE `topics`.`id` = ? [["id", 7]] ``` After: ``` Topic Update All (0.4ms) UPDATE `topics` SET `topics`.`replies_count` = COALESCE(`topics`.`replies_count`, 0) + ?, `topics`.`updated_at` = ? WHERE `topics`.`id` = ? [["replies_count", 1], ["updated_at", 2018-09-27 18:55:05 UTC], ["id", 7]] ``` --- activerecord/lib/active_record/relation.rb | 23 +++++++++++++--------- activerecord/lib/arel/factory_methods.rb | 4 ++++ activerecord/lib/arel/visitors/to_sql.rb | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 771ca952e1..2fc0cb0597 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -379,19 +379,22 @@ def update(id = :all, attributes) # :nodoc: def update_counters(counters) # :nodoc: touch = counters.delete(:touch) - updates = counters.map do |counter_name, value| - operator = value < 0 ? "-" : "+" - quoted_column = connection.quote_table_name_for_assignment(table.name, counter_name) - "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}" + updates = {} + counters.each do |counter_name, value| + attr = arel_attribute(counter_name) + bind = predicate_builder.build_bind_attribute(attr.name, value.abs) + expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attr), 0) + expr = value < 0 ? expr - bind : expr + bind + updates[counter_name] = expr.expr end if touch names = touch if touch != true touch_updates = klass.touch_attributes_with_time(*names) - updates << klass.sanitize_sql_for_assignment(touch_updates, table.name) unless touch_updates.empty? + updates.merge!(touch_updates) unless touch_updates.empty? end - update_all updates.join(", ") + update_all updates end # Touches all records in the current relation without instantiating records first with the updated_at/on attributes @@ -632,9 +635,11 @@ def load_records(records) def _substitute_values(values) values.map do |name, value| attr = arel_attribute(name) - type = klass.type_for_attribute(attr.name) - bind = predicate_builder.build_bind_attribute(attr.name, type.cast(value)) - [attr, bind] + unless value.is_a?(Arel::Node) || value.is_a?(Arel::Attribute) || value.is_a?(Arel::Nodes::SqlLiteral) + type = klass.type_for_attribute(attr.name) + value = predicate_builder.build_bind_attribute(attr.name, type.cast(value)) + end + [attr, value] end end diff --git a/activerecord/lib/arel/factory_methods.rb b/activerecord/lib/arel/factory_methods.rb index b828bc274e..83ec23e403 100644 --- a/activerecord/lib/arel/factory_methods.rb +++ b/activerecord/lib/arel/factory_methods.rb @@ -41,5 +41,9 @@ def grouping(expr) def lower(column) Nodes::NamedFunction.new "LOWER", [Nodes.build_quoted(column)] end + + def coalesce(*exprs) + Nodes::NamedFunction.new "COALESCE", exprs + end end end diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb index 81ca63f261..40c626c725 100644 --- a/activerecord/lib/arel/visitors/to_sql.rb +++ b/activerecord/lib/arel/visitors/to_sql.rb @@ -637,7 +637,7 @@ def visit_Arel_Nodes_Or(o, collector) def visit_Arel_Nodes_Assignment(o, collector) case o.right - when Arel::Nodes::UnqualifiedColumn, Arel::Attributes::Attribute, Arel::Nodes::BindParam + when Arel::Nodes::Node, Arel::Attributes::Attribute collector = visit o.left, collector collector << " = " visit o.right, collector -- GitLab