Ticket #9487: parallel_calculations_with_tests.diff
| File parallel_calculations_with_tests.diff, 5.8 kB (added by jcoglan, 5 months ago) |
|---|
-
activerecord/lib/active_record/calculations.rb
old new 111 111 # Person.average(:age) # SELECT AVG(age) FROM people... 112 112 # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake' 113 113 # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors 114 def calculate(operation, column_name, options = {}) 114 # 115 # You can also execute several calculations using a single query to speed things along. To do this, pass a hash as the first argument. 116 # the +column_name+ argument is omitted in this case. 117 # 118 # Examples: 119 # 120 # Person.calculate(:how_many => [:count, '*'], :total_age => [:sum, :age]) 121 # #=> {:how_many => 12, :total_age => 387} 122 # 123 # Person.calculate({:how_many => [:count, '*'], :total_age => [:sum, :age]}, :conditions => ['age < ?', 30]) 124 # #=> {:how_many => 7, :total_age => 94} 125 def calculate(operation, *args) 126 column_name = operation.is_a?(Hash) ? nil : args.shift 127 options = args.first || {} 128 115 129 validate_calculation_options(operation, options) 116 130 column_name = options[:select] if options[:select] 117 131 column_name = '*' if column_name == :all … … 149 163 end 150 164 151 165 def construct_calculation_sql(operation, column_name, options) #:nodoc: 152 operation = operation.to_s.downcase 166 operation = operation.to_s.downcase unless operation.is_a?(Hash) 153 167 options = options.symbolize_keys 154 168 155 169 scope = scope(:find) … … 167 181 end 168 182 end 169 183 170 sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}" 184 if calculations = extract_query_information_from_operation(operation) 185 sql = "SELECT " + calculations.collect do |calc| 186 calculation_column_name = calc[:column] 187 calculation_column_name = '*' if calc[:column].blank? 188 "#{calc[:operation]}(#{'DISTINCT ' if options[:distinct]}#{calculation_column_name}) AS #{column_alias_for(calc[:operation], calc[:alias])}" 189 end.join(", ") 190 else 191 sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}" 192 end 171 193 172 194 # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. 173 195 sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround … … 204 226 sql 205 227 end 206 228 229 def extract_query_information_from_operation(operation) 230 return nil unless operation.is_a?(Hash) 231 calculations = [] 232 operation.each do |key, value| 233 value = value.to_s.to_a unless value.is_a?(Array) 234 calculations << {:alias => key.to_s, :distinct_alias => "#{key}_#{value[0]}_alias", 235 :operation => value[0].to_s.downcase, :column => value[1].to_s} 236 end 237 calculations 238 end 239 207 240 def execute_simple_calculation(operation, column_name, column, options) #:nodoc: 208 value = connection.select_value(construct_calculation_sql(operation, column_name, options)) 209 type_cast_calculated_value(value, column, operation) 241 results = connection.select_one(construct_calculation_sql(operation, column_name, options)) 242 if operation.is_a?(Hash) 243 calculation_results = {} 244 calculations = extract_query_information_from_operation(operation) 245 results.each do |key, value| 246 calculation = calculations.find { |calc| key == column_alias_for(calc[:operation], calc[:alias]) } 247 calculation_results[calculation[:alias].to_sym] = type_cast_calculated_value(value, column_for(calculation[:column]), calculation[:operation]) 248 end 249 calculation_results 250 else 251 type_cast_calculated_value(results.values.first, column, operation) 252 end 210 253 end 211 254 212 255 def execute_grouped_calculation(operation, column_name, column, options) #:nodoc: -
activerecord/test/cases/calculations_test.rb
old new 248 248 def test_count_with_too_many_parameters_raises 249 249 assert_raise(ArgumentError) { Account.count(1, 2, 3) } 250 250 end 251 252 def test_parallel_calculations 253 account_calculations = Account.calculate(:how_many => :count, :total_credit => [:sum, :credit_limit], :max_limit => [:max, :credit_limit]) 254 assert_equal 6, account_calculations[:how_many] 255 assert_equal 318, account_calculations[:total_credit] 256 assert_equal 60, account_calculations[:max_limit] 257 end 258 259 def test_parallel_calculations_with_conditions 260 account_calculations = Account.calculate({:how_many => [:count, '*'], :total_credit => [:sum, :credit_limit]}, :conditions => {:credit_limit => 50}) 261 assert_equal 3, account_calculations[:how_many] 262 assert_equal 150, account_calculations[:total_credit] 263 end 264 265 def test_parallel_calculations_with_conditions_and_distinct 266 account_calculations = Account.calculate( 267 {:how_many => [:count, :credit_limit], :total_credit => [:sum, :credit_limit]}, 268 :conditions => {:credit_limit => 50}, 269 :distinct => true 270 ) 271 assert_equal 1, account_calculations[:how_many] 272 assert_equal 50, account_calculations[:total_credit] 273 end 251 274 end