activerecord 7.1.6 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +839 -2248
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +16 -8
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +5 -25
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +51 -60
  34. data/lib/active_record/attributes.rb +93 -68
  35. data/lib/active_record/autosave_association.rb +25 -32
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +294 -72
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +19 -18
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +30 -8
  60. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +68 -49
  76. data/lib/active_record/core.rb +112 -44
  77. data/lib/active_record/counter_cache.rb +19 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  79. data/lib/active_record/database_configurations/database_config.rb +19 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +42 -18
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
  87. data/lib/active_record/encryption/encryptor.rb +35 -19
  88. data/lib/active_record/encryption/key_provider.rb +1 -1
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/enum.rb +31 -13
  94. data/lib/active_record/errors.rb +49 -23
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixture_set/table_row.rb +19 -2
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +8 -4
  99. data/lib/active_record/gem_version.rb +2 -2
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +7 -6
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +87 -77
  112. data/lib/active_record/model_schema.rb +31 -68
  113. data/lib/active_record/nested_attributes.rb +11 -3
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +19 -8
  117. data/lib/active_record/query_logs.rb +19 -0
  118. data/lib/active_record/querying.rb +25 -13
  119. data/lib/active_record/railtie.rb +39 -57
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +42 -44
  122. data/lib/active_record/reflection.rb +98 -36
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +14 -8
  125. data/lib/active_record/relation/calculations.rb +127 -89
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +26 -12
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  131. data/lib/active_record/relation/predicate_builder.rb +3 -3
  132. data/lib/active_record/relation/query_attribute.rb +1 -1
  133. data/lib/active_record/relation/query_methods.rb +238 -65
  134. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  135. data/lib/active_record/relation/spawn_methods.rb +2 -18
  136. data/lib/active_record/relation/where_clause.rb +15 -21
  137. data/lib/active_record/relation.rb +508 -74
  138. data/lib/active_record/result.rb +31 -44
  139. data/lib/active_record/runtime_registry.rb +39 -0
  140. data/lib/active_record/sanitization.rb +24 -19
  141. data/lib/active_record/schema.rb +8 -6
  142. data/lib/active_record/schema_dumper.rb +48 -20
  143. data/lib/active_record/schema_migration.rb +30 -14
  144. data/lib/active_record/scoping/named.rb +1 -0
  145. data/lib/active_record/secure_token.rb +3 -3
  146. data/lib/active_record/signed_id.rb +27 -7
  147. data/lib/active_record/statement_cache.rb +7 -7
  148. data/lib/active_record/table_metadata.rb +1 -10
  149. data/lib/active_record/tasks/database_tasks.rb +69 -41
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
  152. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  153. data/lib/active_record/test_fixtures.rb +86 -89
  154. data/lib/active_record/testing/query_assertions.rb +121 -0
  155. data/lib/active_record/timestamp.rb +2 -2
  156. data/lib/active_record/token_for.rb +22 -12
  157. data/lib/active_record/touch_later.rb +1 -1
  158. data/lib/active_record/transaction.rb +132 -0
  159. data/lib/active_record/transactions.rb +73 -15
  160. data/lib/active_record/translation.rb +0 -2
  161. data/lib/active_record/type/serialized.rb +1 -3
  162. data/lib/active_record/type_caster/connection.rb +4 -4
  163. data/lib/active_record/validations/associated.rb +9 -3
  164. data/lib/active_record/validations/uniqueness.rb +15 -10
  165. data/lib/active_record/validations.rb +4 -1
  166. data/lib/active_record.rb +148 -39
  167. data/lib/arel/alias_predication.rb +1 -1
  168. data/lib/arel/collectors/bind.rb +3 -1
  169. data/lib/arel/collectors/composite.rb +7 -0
  170. data/lib/arel/collectors/sql_string.rb +1 -1
  171. data/lib/arel/collectors/substitute_binds.rb +1 -1
  172. data/lib/arel/crud.rb +2 -0
  173. data/lib/arel/delete_manager.rb +5 -0
  174. data/lib/arel/nodes/binary.rb +0 -6
  175. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  176. data/lib/arel/nodes/delete_statement.rb +4 -2
  177. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  178. data/lib/arel/nodes/node.rb +4 -3
  179. data/lib/arel/nodes/sql_literal.rb +7 -0
  180. data/lib/arel/nodes/update_statement.rb +4 -2
  181. data/lib/arel/nodes.rb +2 -2
  182. data/lib/arel/predications.rb +1 -1
  183. data/lib/arel/select_manager.rb +7 -3
  184. data/lib/arel/tree_manager.rb +3 -2
  185. data/lib/arel/update_manager.rb +7 -1
  186. data/lib/arel/visitors/dot.rb +3 -0
  187. data/lib/arel/visitors/mysql.rb +9 -4
  188. data/lib/arel/visitors/postgresql.rb +1 -12
  189. data/lib/arel/visitors/sqlite.rb +25 -0
  190. data/lib/arel/visitors/to_sql.rb +31 -16
  191. data/lib/arel.rb +7 -3
  192. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  193. metadata +16 -10
@@ -60,37 +60,37 @@ module ActiveRecord
60
60
  # Person.distinct.count(:age)
61
61
  # # => counts the number of different age values
62
62
  #
63
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
63
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
64
64
  # it returns a Hash whose keys represent the aggregated column,
65
65
  # and the values are the respective amounts:
66
66
  #
67
67
  # Person.group(:city).count
68
68
  # # => { 'Rome' => 5, 'Paris' => 3 }
69
69
  #
70
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
70
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
71
71
  # keys are an array containing the individual values of each column and the value
72
- # of each key would be the #count.
72
+ # of each key would be the count.
73
73
  #
74
74
  # Article.group(:status, :category).count
75
75
  # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
76
76
  #
77
- # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
77
+ # If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
78
78
  #
79
79
  # Person.select(:age).count
80
80
  # # => counts the number of different age values
81
81
  #
82
- # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
82
+ # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid +count+ expressions. The specifics differ
83
83
  # between databases. In invalid cases, an error from the database is thrown.
84
84
  #
85
- # When given a block, loads all records in the relation, if the relation
86
- # hasn't been loaded yet. Calls the block with each record in the relation.
87
- # Returns the number of records for which the block returns a truthy value.
85
+ # When given a block, calls the block with each record in the relation and
86
+ # returns the number of records for which the block returns a truthy value.
88
87
  #
89
88
  # Person.count { |person| person.age > 21 }
90
89
  # # => counts the number of people older that 21
91
90
  #
92
- # Note: If there are a lot of records in the relation, loading all records
93
- # could result in performance issues.
91
+ # If the relation hasn't been loaded yet, calling +count+ with a block will
92
+ # load all records in the relation. If there are a lot of records in the
93
+ # relation, loading all records could result in performance issues.
94
94
  def count(column_name = nil)
95
95
  if block_given?
96
96
  unless column_name.nil?
@@ -159,16 +159,15 @@ module ActiveRecord
159
159
  #
160
160
  # Person.sum(:age) # => 4562
161
161
  #
162
- # When given a block, loads all records in the relation, if the relation
163
- # hasn't been loaded yet. Calls the block with each record in the relation.
164
- # Returns the sum of +initial_value_or_column+ and the block return
165
- # values:
162
+ # When given a block, calls the block with each record in the relation and
163
+ # returns the sum of +initial_value_or_column+ plus the block return values:
166
164
  #
167
165
  # Person.sum { |person| person.age } # => 4562
168
166
  # Person.sum(1000) { |person| person.age } # => 5562
169
167
  #
170
- # Note: If there are a lot of records in the relation, loading all records
171
- # could result in performance issues.
168
+ # If the relation hasn't been loaded yet, calling +sum+ with a block will
169
+ # load all records in the relation. If there are a lot of records in the
170
+ # relation, loading all records could result in performance issues.
172
171
  def sum(initial_value_or_column = 0, &block)
173
172
  if block_given?
174
173
  map(&block).sum(initial_value_or_column)
@@ -234,7 +233,7 @@ module ActiveRecord
234
233
  if operation == "count"
235
234
  unless distinct_value || distinct_select?(column_name || select_for_count)
236
235
  relation.distinct!
237
- relation.select_values = [ klass.primary_key || table[Arel.star] ]
236
+ relation.select_values = Array(klass.primary_key || table[Arel.star])
238
237
  end
239
238
  # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
240
239
  relation.order_values = [] if group_values.empty?
@@ -275,6 +274,10 @@ module ActiveRecord
275
274
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
276
275
  # # => [2, 3]
277
276
  #
277
+ # Comment.joins(:person).pluck(:id, person: [:id])
278
+ # # SELECT comments.id, people.id FROM comments INNER JOIN people on comments.person_id = people.id
279
+ # # => [[1, 2], [2, 2]]
280
+ #
278
281
  # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
279
282
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
280
283
  # # => ['0', '27761', '173']
@@ -302,15 +305,17 @@ module ActiveRecord
302
305
  relation = apply_join_dependency
303
306
  relation.pluck(*column_names)
304
307
  else
305
- klass.disallow_raw_sql!(column_names.flatten)
306
- columns = arel_columns(column_names)
308
+ klass.disallow_raw_sql!(flattened_args(column_names))
307
309
  relation = spawn
310
+ columns = relation.arel_columns(column_names)
308
311
  relation.select_values = columns
309
312
  result = skip_query_cache_if_necessary do
310
313
  if where_clause.contradiction?
311
314
  ActiveRecord::Result.empty(async: @async)
312
315
  else
313
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
316
+ klass.with_connection do |c|
317
+ c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
318
+ end
314
319
  end
315
320
  end
316
321
  result.then do |result|
@@ -357,7 +362,7 @@ module ActiveRecord
357
362
  # Returns the base model's ID's for the relation using the table's primary key
358
363
  #
359
364
  # Person.ids # SELECT people.id FROM people
360
- # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
365
+ # Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
361
366
  def ids
362
367
  primary_key_array = Array(primary_key)
363
368
 
@@ -385,7 +390,9 @@ module ActiveRecord
385
390
  ActiveRecord::Result.empty
386
391
  else
387
392
  skip_query_cache_if_necessary do
388
- klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
393
+ klass.with_connection do |c|
394
+ c.select_all(relation, "#{klass.name} Ids", async: @async)
395
+ end
389
396
  end
390
397
  end
391
398
 
@@ -398,6 +405,15 @@ module ActiveRecord
398
405
  async.ids
399
406
  end
400
407
 
408
+ protected
409
+ def aggregate_column(column_name)
410
+ return column_name if Arel::Expressions === column_name
411
+
412
+ arel_column(column_name.to_s) do |name|
413
+ column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
414
+ end
415
+ end
416
+
401
417
  private
402
418
  def all_attributes?(column_names)
403
419
  (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
@@ -438,20 +454,12 @@ module ActiveRecord
438
454
  column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
439
455
  end
440
456
 
441
- def aggregate_column(column_name)
442
- return column_name if Arel::Expressions === column_name
443
-
444
- arel_column(column_name.to_s) do |name|
445
- Arel.sql(column_name == :all ? "*" : name)
446
- end
447
- end
448
-
449
457
  def operation_over_aggregate_column(column, operation, distinct)
450
458
  operation == "count" ? column.count(distinct) : column.public_send(operation)
451
459
  end
452
460
 
453
461
  def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
454
- if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
462
+ if build_count_subquery?(operation, column_name, distinct)
455
463
  # Shortcut when limit is zero.
456
464
  return 0 if limit_value == 0
457
465
 
@@ -461,7 +469,7 @@ module ActiveRecord
461
469
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
462
470
  relation = unscope(:order).distinct!(false)
463
471
 
464
- column = aggregate_column(column_name)
472
+ column = relation.aggregate_column(column_name)
465
473
  select_value = operation_over_aggregate_column(column, operation, distinct)
466
474
  select_value.distinct = true if operation == "sum" && distinct
467
475
 
@@ -471,10 +479,16 @@ module ActiveRecord
471
479
  end
472
480
 
473
481
  query_result = if relation.where_clause.contradiction?
474
- ActiveRecord::Result.empty
482
+ if @async
483
+ FutureResult.wrap(ActiveRecord::Result.empty)
484
+ else
485
+ ActiveRecord::Result.empty
486
+ end
475
487
  else
476
488
  skip_query_cache_if_necessary do
477
- @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
489
+ @klass.with_connection do |c|
490
+ c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
491
+ end
478
492
  end
479
493
  end
480
494
 
@@ -498,70 +512,76 @@ module ActiveRecord
498
512
  associated = association && association.belongs_to? # only count belongs_to associations
499
513
  group_fields = Array(association.foreign_key) if associated
500
514
  end
501
- group_fields = arel_columns(group_fields)
502
515
 
503
- column_alias_tracker = ColumnAliasTracker.new(connection)
516
+ relation = except(:group).distinct!(false)
517
+ group_fields = relation.arel_columns(group_fields)
504
518
 
505
- group_aliases = group_fields.map { |field|
506
- field = connection.visitor.compile(field) if Arel.arel_node?(field)
507
- column_alias_tracker.alias_for(field.to_s.downcase)
508
- }
509
- group_columns = group_aliases.zip(group_fields)
519
+ @klass.with_connection do |connection|
520
+ column_alias_tracker = ColumnAliasTracker.new(connection)
510
521
 
511
- column = aggregate_column(column_name)
512
- column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
513
- select_value = operation_over_aggregate_column(column, operation, distinct)
514
- select_value.as(connection.quote_column_name(column_alias))
522
+ group_aliases = group_fields.map { |field|
523
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
524
+ column_alias_tracker.alias_for(field.to_s.downcase)
525
+ }
526
+ group_columns = group_aliases.zip(group_fields)
515
527
 
516
- select_values = [select_value]
517
- select_values += self.select_values unless having_clause.empty?
528
+ column = relation.aggregate_column(column_name)
529
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
530
+ select_value = operation_over_aggregate_column(column, operation, distinct)
531
+ select_value.as(adapter_class.quote_column_name(column_alias))
518
532
 
519
- select_values.concat group_columns.map { |aliaz, field|
520
- aliaz = connection.quote_column_name(aliaz)
521
- if field.respond_to?(:as)
522
- field.as(aliaz)
523
- else
524
- "#{field} AS #{aliaz}"
525
- end
526
- }
533
+ select_values = [select_value]
534
+ select_values += self.select_values unless having_clause.empty?
527
535
 
528
- relation = except(:group).distinct!(false)
529
- relation.group_values = group_fields
530
- relation.select_values = select_values
531
-
532
- result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
533
- result.then do |calculated_data|
534
- if association
535
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
536
- key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
537
- key_records = key_records.index_by(&:id)
538
- end
536
+ select_values.concat group_columns.map { |aliaz, field|
537
+ aliaz = adapter_class.quote_column_name(aliaz)
538
+ if field.respond_to?(:as)
539
+ field.as(aliaz)
540
+ else
541
+ "#{field} AS #{aliaz}"
542
+ end
543
+ }
539
544
 
540
- key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
541
- types[aliaz] = col_name.try(:type_caster) ||
542
- type_for(col_name) do
543
- calculated_data.column_types.fetch(aliaz, Type.default_value)
544
- end
545
+ relation.group_values = group_fields
546
+ relation.select_values = select_values
547
+
548
+ result = skip_query_cache_if_necessary do
549
+ connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
545
550
  end
546
551
 
547
- hash_rows = calculated_data.cast_values(key_types).map! do |row|
548
- calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
549
- hash[col_name] = row[i]
552
+ result.then do |calculated_data|
553
+ if association
554
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
555
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
556
+ key_records = key_records.index_by(&:id)
550
557
  end
551
- end
552
558
 
553
- if operation != "count"
554
- type = column.try(:type_caster) ||
555
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
556
- type = type.subtype if Enum::EnumType === type
557
- end
559
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
560
+ types[aliaz] = col_name.try(:type_caster) ||
561
+ type_for(col_name) do
562
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
563
+ end
564
+ end
558
565
 
559
- hash_rows.each_with_object({}) do |row, result|
560
- key = group_aliases.map { |aliaz| row[aliaz] }
561
- key = key.first if key.size == 1
562
- key = key_records[key] if associated
566
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
567
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
568
+ hash[col_name] = row[i]
569
+ end
570
+ end
571
+
572
+ if operation != "count"
573
+ type = column.try(:type_caster) ||
574
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
575
+ type = type.subtype if Enum::EnumType === type
576
+ end
577
+
578
+ hash_rows.each_with_object({}) do |row, result|
579
+ key = group_aliases.map { |aliaz| row[aliaz] }
580
+ key = key.first if key.size == 1
581
+ key = key_records[key] if associated
563
582
 
564
- result[key] = type_cast_calculated_value(row[column_alias], operation, type)
583
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
584
+ end
565
585
  end
566
586
  end
567
587
  end
@@ -589,7 +609,7 @@ module ActiveRecord
589
609
  klass.attribute_types.fetch(name = result.columns[i]) do
590
610
  join_dependencies ||= build_join_dependencies
591
611
  lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
592
- result.column_types[name] || Type.default_value
612
+ result.column_types[i] || Type.default_value
593
613
  end
594
614
  end
595
615
  end
@@ -617,22 +637,40 @@ module ActiveRecord
617
637
  def select_for_count
618
638
  if select_values.present?
619
639
  return select_values.first if select_values.one?
620
- select_values.join(", ")
640
+
641
+ select_values.map do |field|
642
+ column = arel_column(field.to_s) do |attr_name|
643
+ Arel.sql(attr_name)
644
+ end
645
+
646
+ if column.is_a?(Arel::Nodes::SqlLiteral)
647
+ column
648
+ else
649
+ "#{adapter_class.quote_table_name(column.relation.name)}.#{adapter_class.quote_column_name(column.name)}"
650
+ end
651
+ end.join(", ")
621
652
  else
622
653
  :all
623
654
  end
624
655
  end
625
656
 
657
+ def build_count_subquery?(operation, column_name, distinct)
658
+ # SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
659
+ # multiple columns, so we need to use subquery for this.
660
+ operation == "count" &&
661
+ (((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
662
+ end
663
+
626
664
  def build_count_subquery(relation, column_name, distinct)
627
665
  if column_name == :all
628
666
  column_alias = Arel.star
629
667
  relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
630
668
  else
631
669
  column_alias = Arel.sql("count_column")
632
- relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
670
+ relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
633
671
  end
634
672
 
635
- subquery_alias = Arel.sql("subquery_for_count")
673
+ subquery_alias = Arel.sql("subquery_for_count", retryable: true)
636
674
  select_value = operation_over_aggregate_column(column_alias, "count", false)
637
675
 
638
676
  relation.build_subquery(subquery_alias, select_value)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
4
3
  require "active_support/core_ext/module/delegation"
5
4
 
6
5
  module ActiveRecord
@@ -67,23 +66,22 @@ module ActiveRecord
67
66
  end
68
67
 
69
68
  class GeneratedRelationMethods < Module # :nodoc:
70
- include Mutex_m
69
+ MUTEX = Mutex.new
71
70
 
72
71
  def generate_method(method)
73
- synchronize do
72
+ MUTEX.synchronize do
74
73
  return if method_defined?(method)
75
74
 
76
- if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
75
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
77
76
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
78
77
  def #{method}(...)
79
78
  scoping { klass.#{method}(...) }
80
79
  end
81
80
  RUBY
82
81
  else
83
- define_method(method) do |*args, &block|
84
- scoping { klass.public_send(method, *args, &block) }
82
+ define_method(method) do |*args, **kwargs, &block|
83
+ scoping { klass.public_send(method, *args, **kwargs, &block) }
85
84
  end
86
- ruby2_keywords(method)
87
85
  end
88
86
  end
89
87
  end
@@ -102,7 +100,7 @@ module ActiveRecord
102
100
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
103
101
  :shuffle, :split, :slice, :index, :rindex, to: :records
104
102
 
105
- delegate :primary_key, :connection, :transaction, to: :klass
103
+ delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
106
104
 
107
105
  module ClassSpecificRelation # :nodoc:
108
106
  extend ActiveSupport::Concern
@@ -114,17 +112,16 @@ module ActiveRecord
114
112
  end
115
113
 
116
114
  private
117
- def method_missing(method, *args, &block)
115
+ def method_missing(method, ...)
118
116
  if @klass.respond_to?(method)
119
117
  unless Delegation.uncacheable_methods.include?(method)
120
118
  @klass.generate_relation_method(method)
121
119
  end
122
- scoping { @klass.public_send(method, *args, &block) }
120
+ scoping { @klass.public_send(method, ...) }
123
121
  else
124
122
  super
125
123
  end
126
124
  end
127
- ruby2_keywords(:method_missing)
128
125
  end
129
126
 
130
127
  module ClassMethods # :nodoc:
@@ -24,22 +24,22 @@ module ActiveRecord
24
24
  # TravelRoute.primary_key = [:origin, :destination]
25
25
  #
26
26
  # TravelRoute.find(["Ottawa", "London"])
27
- # => #<TravelRoute origin: "Ottawa", destination: "London">
27
+ # # => #<TravelRoute origin: "Ottawa", destination: "London">
28
28
  #
29
29
  # TravelRoute.find([["Paris", "Montreal"]])
30
- # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
30
+ # # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
31
31
  #
32
32
  # TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
33
- # => [
34
- # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
- # #<TravelRoute origin: "New York", destination: "Portland">
36
- # ]
33
+ # # => [
34
+ # # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
+ # # #<TravelRoute origin: "New York", destination: "Portland">
36
+ # # ]
37
37
  #
38
38
  # TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
39
- # => [
40
- # #<TravelRoute origin: "Berlin", destination: "London">,
41
- # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
- # ]
39
+ # # => [
40
+ # # #<TravelRoute origin: "Berlin", destination: "London">,
41
+ # # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
+ # # ]
43
43
  #
44
44
  # NOTE: The returned records are in the same order as the ids you provide.
45
45
  # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
@@ -87,6 +87,14 @@ module ActiveRecord
87
87
  #
88
88
  # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
89
89
  # # returns an Array of the required fields.
90
+ #
91
+ # ==== Edge Cases
92
+ #
93
+ # Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
94
+ # Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
95
+ # Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
96
+ # Person.find([]) # returns an empty array if the argument is an empty array.
97
+ # Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
90
98
  def find(*args)
91
99
  return super if block_given?
92
100
  find_with_ids(*args)
@@ -366,7 +374,11 @@ module ActiveRecord
366
374
  relation = construct_relation_for_exists(conditions)
367
375
  return false if relation.where_clause.contradiction?
368
376
 
369
- skip_query_cache_if_necessary { connection.select_rows(relation.arel, "#{name} Exists?").size == 1 }
377
+ skip_query_cache_if_necessary do
378
+ with_connection do |c|
379
+ c.select_rows(relation.arel, "#{name} Exists?").size == 1
380
+ end
381
+ end
370
382
  end
371
383
 
372
384
  # Returns true if the relation contains the given record or false otherwise.
@@ -460,7 +472,9 @@ module ActiveRecord
460
472
  )
461
473
  )
462
474
  relation = skip_query_cache_if_necessary do
463
- klass.connection.distinct_relation_for_primary_key(relation)
475
+ klass.with_connection do |c|
476
+ c.distinct_relation_for_primary_key(relation)
477
+ end
464
478
  end
465
479
  end
466
480
 
@@ -7,16 +7,15 @@ module ActiveRecord
7
7
  class HashMerger # :nodoc:
8
8
  attr_reader :relation, :hash
9
9
 
10
- def initialize(relation, hash, rewhere = nil)
10
+ def initialize(relation, hash)
11
11
  hash.assert_valid_keys(*Relation::VALUE_METHODS)
12
12
 
13
13
  @relation = relation
14
14
  @hash = hash
15
- @rewhere = rewhere
16
15
  end
17
16
 
18
17
  def merge
19
- Merger.new(relation, other, @rewhere).merge
18
+ Merger.new(relation, other).merge
20
19
  end
21
20
 
22
21
  # Applying values to a relation has some side effects. E.g.
@@ -44,11 +43,10 @@ module ActiveRecord
44
43
  class Merger # :nodoc:
45
44
  attr_reader :relation, :values, :other
46
45
 
47
- def initialize(relation, other, rewhere = nil)
46
+ def initialize(relation, other)
48
47
  @relation = relation
49
48
  @values = other.values
50
49
  @other = other
51
- @rewhere = rewhere
52
50
  end
53
51
 
54
52
  NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS -
@@ -178,7 +176,7 @@ module ActiveRecord
178
176
  def merge_clauses
179
177
  relation.from_clause = other.from_clause if replace_from_clause?
180
178
 
181
- where_clause = relation.where_clause.merge(other.where_clause, @rewhere)
179
+ where_clause = relation.where_clause.merge(other.where_clause)
182
180
  relation.where_clause = where_clause unless where_clause.empty?
183
181
 
184
182
  having_clause = relation.having_clause.merge(other.having_clause)
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  return attribute.in([]) if value.empty?
14
14
 
15
15
  values = value.map { |x| x.is_a?(Base) ? x.id : x }
16
- nils = values.extract!(&:nil?)
16
+ nils = values.compact!
17
17
  ranges = values.extract! { |v| v.is_a?(Range) }
18
18
 
19
19
  values_predicate =
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
24
24
  end
25
25
 
26
- unless nils.empty?
26
+ if nils
27
27
  values_predicate = values_predicate.or(attribute.eq(nil))
28
28
  end
29
29
 
@@ -57,9 +57,17 @@ module ActiveRecord
57
57
  end
58
58
 
59
59
  def convert_to_id(value)
60
- return primary_key.map { |pk| value.public_send(pk) } if primary_key.is_a?(Array)
60
+ if primary_key.is_a?(Array)
61
+ primary_key.map do |attribute|
62
+ next nil if value.nil?
61
63
 
62
- if value.respond_to?(primary_key)
64
+ if attribute == "id"
65
+ value.id_value
66
+ else
67
+ value.public_send(attribute)
68
+ end
69
+ end
70
+ elsif value.respond_to?(primary_key)
63
71
  value.public_send(primary_key)
64
72
  else
65
73
  value
@@ -28,9 +28,9 @@ module ActiveRecord
28
28
  def self.references(attributes)
29
29
  attributes.each_with_object([]) do |(key, value), result|
30
30
  if value.is_a?(Hash)
31
- result << Arel.sql(key)
31
+ result << Arel.sql(key, retryable: true)
32
32
  elsif (idx = key.rindex("."))
33
- result << Arel.sql(key[0, idx])
33
+ result << Arel.sql(key[0, idx], retryable: true)
34
34
  end
35
35
  end
36
36
  end
@@ -142,7 +142,7 @@ module ActiveRecord
142
142
  queries.first
143
143
  else
144
144
  queries.map! { |query| query.reduce(&:and) }
145
- queries = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) }
145
+ queries = Arel::Nodes::Or.new(queries)
146
146
  Arel::Nodes::Grouping.new(queries)
147
147
  end
148
148
  end
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  def nil?
36
36
  unless value_before_type_cast.is_a?(StatementCache::Substitute)
37
37
  value_before_type_cast.nil? ||
38
- type.respond_to?(:subtype) && serializable? && value_for_database.nil?
38
+ (type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil?
39
39
  end
40
40
  end
41
41