activerecord 8.0.3 → 8.1.0

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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +538 -512
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +2 -0
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/errors.rb +3 -0
  15. data/lib/active_record/associations/join_dependency.rb +2 -0
  16. data/lib/active_record/associations/preloader/branch.rb +1 -0
  17. data/lib/active_record/associations.rb +159 -21
  18. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  19. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  20. data/lib/active_record/attributes.rb +3 -0
  21. data/lib/active_record/autosave_association.rb +1 -1
  22. data/lib/active_record/base.rb +0 -2
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
  45. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  46. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
  47. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  48. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  50. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  51. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  52. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
  53. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
  54. data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
  55. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  56. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
  57. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  58. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  59. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  60. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  61. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  62. data/lib/active_record/connection_adapters.rb +1 -0
  63. data/lib/active_record/connection_handling.rb +14 -9
  64. data/lib/active_record/core.rb +5 -4
  65. data/lib/active_record/counter_cache.rb +33 -8
  66. data/lib/active_record/database_configurations/database_config.rb +5 -1
  67. data/lib/active_record/database_configurations/hash_config.rb +53 -9
  68. data/lib/active_record/database_configurations/url_config.rb +13 -3
  69. data/lib/active_record/database_configurations.rb +7 -3
  70. data/lib/active_record/delegated_type.rb +1 -1
  71. data/lib/active_record/dynamic_matchers.rb +54 -69
  72. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  73. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  74. data/lib/active_record/encryption/encryptor.rb +12 -0
  75. data/lib/active_record/encryption/scheme.rb +1 -1
  76. data/lib/active_record/enum.rb +24 -8
  77. data/lib/active_record/errors.rb +20 -4
  78. data/lib/active_record/explain.rb +1 -1
  79. data/lib/active_record/explain_registry.rb +51 -2
  80. data/lib/active_record/filter_attribute_handler.rb +73 -0
  81. data/lib/active_record/fixtures.rb +2 -2
  82. data/lib/active_record/gem_version.rb +2 -2
  83. data/lib/active_record/inheritance.rb +1 -1
  84. data/lib/active_record/insert_all.rb +12 -7
  85. data/lib/active_record/locking/optimistic.rb +7 -0
  86. data/lib/active_record/locking/pessimistic.rb +5 -0
  87. data/lib/active_record/log_subscriber.rb +2 -6
  88. data/lib/active_record/middleware/shard_selector.rb +34 -17
  89. data/lib/active_record/migration/command_recorder.rb +14 -1
  90. data/lib/active_record/migration/compatibility.rb +34 -24
  91. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  92. data/lib/active_record/migration.rb +26 -16
  93. data/lib/active_record/model_schema.rb +36 -10
  94. data/lib/active_record/nested_attributes.rb +2 -0
  95. data/lib/active_record/persistence.rb +34 -3
  96. data/lib/active_record/query_cache.rb +22 -15
  97. data/lib/active_record/query_logs.rb +3 -7
  98. data/lib/active_record/railtie.rb +32 -3
  99. data/lib/active_record/railties/controller_runtime.rb +11 -6
  100. data/lib/active_record/railties/databases.rake +15 -3
  101. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  102. data/lib/active_record/railties/job_runtime.rb +10 -11
  103. data/lib/active_record/reflection.rb +35 -0
  104. data/lib/active_record/relation/batches.rb +25 -11
  105. data/lib/active_record/relation/calculations.rb +20 -9
  106. data/lib/active_record/relation/delegation.rb +0 -1
  107. data/lib/active_record/relation/finder_methods.rb +27 -11
  108. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  109. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  110. data/lib/active_record/relation/predicate_builder.rb +9 -7
  111. data/lib/active_record/relation/query_attribute.rb +3 -1
  112. data/lib/active_record/relation/query_methods.rb +40 -29
  113. data/lib/active_record/relation/where_clause.rb +1 -8
  114. data/lib/active_record/relation.rb +24 -12
  115. data/lib/active_record/result.rb +44 -21
  116. data/lib/active_record/runtime_registry.rb +41 -58
  117. data/lib/active_record/sanitization.rb +2 -0
  118. data/lib/active_record/schema_dumper.rb +12 -10
  119. data/lib/active_record/scoping.rb +0 -1
  120. data/lib/active_record/signed_id.rb +43 -15
  121. data/lib/active_record/statement_cache.rb +13 -9
  122. data/lib/active_record/store.rb +44 -19
  123. data/lib/active_record/structured_event_subscriber.rb +85 -0
  124. data/lib/active_record/table_metadata.rb +5 -20
  125. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  126. data/lib/active_record/tasks/database_tasks.rb +25 -34
  127. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  128. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
  129. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  130. data/lib/active_record/test_databases.rb +14 -4
  131. data/lib/active_record/test_fixtures.rb +27 -2
  132. data/lib/active_record/testing/query_assertions.rb +8 -2
  133. data/lib/active_record/timestamp.rb +4 -2
  134. data/lib/active_record/transaction.rb +2 -5
  135. data/lib/active_record/transactions.rb +32 -10
  136. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  137. data/lib/active_record/type/internal/timezone.rb +7 -0
  138. data/lib/active_record/type/json.rb +15 -2
  139. data/lib/active_record/type/serialized.rb +11 -4
  140. data/lib/active_record/type/type_map.rb +1 -1
  141. data/lib/active_record/type_caster/connection.rb +2 -1
  142. data/lib/active_record/validations/associated.rb +1 -1
  143. data/lib/active_record.rb +65 -3
  144. data/lib/arel/alias_predication.rb +2 -0
  145. data/lib/arel/crud.rb +6 -11
  146. data/lib/arel/nodes/count.rb +2 -2
  147. data/lib/arel/nodes/function.rb +4 -10
  148. data/lib/arel/nodes/named_function.rb +2 -2
  149. data/lib/arel/nodes/node.rb +1 -1
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +7 -2
  152. data/lib/arel/visitors/dot.rb +0 -3
  153. data/lib/arel/visitors/postgresql.rb +55 -0
  154. data/lib/arel/visitors/sqlite.rb +55 -8
  155. data/lib/arel/visitors/to_sql.rb +3 -21
  156. data/lib/arel.rb +3 -1
  157. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  158. metadata +14 -10
  159. data/lib/active_record/explain_subscriber.rb +0 -34
  160. data/lib/active_record/normalization.rb +0 -163
@@ -435,35 +435,49 @@ module ActiveRecord
435
435
  if load
436
436
  records = batch_relation.records
437
437
  values = records.pluck(*cursor)
438
- yielded_relation = where(cursor => values)
438
+ values_size = values.size
439
+ values_last = values.last
440
+ yielded_relation = rewhere(cursor => values)
439
441
  yielded_relation.load_records(records)
440
442
  elsif (empty_scope && use_ranges != false) || use_ranges
441
- values = batch_relation.pluck(*cursor)
443
+ # Efficiently peak at the last value for the next batch using offset and limit.
444
+ values_size = batch_limit
445
+ values_last = batch_relation.offset(batch_limit - 1).pick(*cursor)
446
+
447
+ # If the last value is not found using offset, there is at most one more batch of size < batch_limit.
448
+ # Retry by getting the whole list of remaining values so that we have the exact size and last value.
449
+ unless values_last
450
+ values = batch_relation.pluck(*cursor)
451
+ values_size = values.size
452
+ values_last = values.last
453
+ end
442
454
 
443
- finish = values.last
444
- if finish
445
- yielded_relation = apply_finish_limit(batch_relation, cursor, finish, batch_orders)
455
+ # Finally, build the yielded relation if at least one value found.
456
+ if values_last
457
+ yielded_relation = apply_finish_limit(batch_relation, cursor, values_last, batch_orders)
446
458
  yielded_relation = yielded_relation.except(:limit, :order)
447
459
  yielded_relation.skip_query_cache!(false)
448
460
  end
449
461
  else
450
462
  values = batch_relation.pluck(*cursor)
451
- yielded_relation = where(cursor => values)
463
+ values_size = values.size
464
+ values_last = values.last
465
+ yielded_relation = rewhere(cursor => values)
452
466
  end
453
467
 
454
- break if values.empty?
468
+ break if values_size == 0
455
469
 
456
- if values.flatten.any?(nil)
470
+ if [values_last].flatten.any?(nil)
457
471
  raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\
458
472
  "or some columns contain nil."
459
473
  end
460
474
 
461
475
  yield yielded_relation
462
476
 
463
- break if values.length < batch_limit
477
+ break if values_size < batch_limit
464
478
 
465
479
  if limit_value
466
- remaining -= values.length
480
+ remaining -= values_size
467
481
 
468
482
  if remaining == 0
469
483
  # Saves a useless iteration when the limit is a multiple of the
@@ -481,7 +495,7 @@ module ActiveRecord
481
495
  end
482
496
  operators << (last_order == :desc ? :lt : :gt)
483
497
 
484
- cursor_value = values.last
498
+ cursor_value = values_last
485
499
  batch_relation = batch_condition(relation, cursor, cursor_value, operators)
486
500
  end
487
501
 
@@ -286,6 +286,11 @@ module ActiveRecord
286
286
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
287
287
  # # => ['0', '27761', '173']
288
288
  #
289
+ # Be aware that #pluck ignores any previous select clauses
290
+ #
291
+ # Person.select(:name).pluck(:id)
292
+ # # SELECT people.id FROM people
293
+ #
289
294
  # See also #ids.
290
295
  def pluck(*column_names)
291
296
  if @none
@@ -314,7 +319,7 @@ module ActiveRecord
314
319
  columns = relation.arel_columns(column_names)
315
320
  relation.select_values = columns
316
321
  result = skip_query_cache_if_necessary do
317
- if where_clause.contradiction?
322
+ if where_clause.contradiction? && !possible_aggregation?(column_names)
318
323
  ActiveRecord::Result.empty(async: @async)
319
324
  else
320
325
  model.with_connection do |c|
@@ -417,7 +422,7 @@ module ActiveRecord
417
422
  when :all
418
423
  Arel.star
419
424
  else
420
- arel_column(column_name)
425
+ arel_column(column_name.to_s)
421
426
  end
422
427
  end
423
428
 
@@ -461,6 +466,16 @@ module ActiveRecord
461
466
  column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
462
467
  end
463
468
 
469
+ def possible_aggregation?(column_names)
470
+ column_names.all? do |column_name|
471
+ if column_name.is_a?(String)
472
+ column_name.include?("(")
473
+ else
474
+ Arel.arel_node?(column_name)
475
+ end
476
+ end
477
+ end
478
+
464
479
  def operation_over_aggregate_column(column, operation, distinct)
465
480
  operation == "count" ? column.count(distinct) : column.public_send(operation)
466
481
  end
@@ -512,7 +527,6 @@ module ActiveRecord
512
527
 
513
528
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
514
529
  group_fields = group_values
515
- group_fields = group_fields.uniq if group_fields.size > 1
516
530
 
517
531
  if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
518
532
  association = model._reflect_on_association(group_fields.first)
@@ -535,7 +549,7 @@ module ActiveRecord
535
549
  column = relation.aggregate_column(column_name)
536
550
  column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
537
551
  select_value = operation_over_aggregate_column(column, operation, distinct)
538
- select_value.as(model.adapter_class.quote_column_name(column_alias))
552
+ select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
539
553
 
540
554
  select_values = [select_value]
541
555
  select_values += self.select_values unless having_clause.empty?
@@ -662,6 +676,7 @@ module ActiveRecord
662
676
  if column_name == :all
663
677
  column_alias = Arel.star
664
678
  relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
679
+ relation.unscope!(:order)
665
680
  else
666
681
  column_alias = Arel.sql("count_column")
667
682
  relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
@@ -670,11 +685,7 @@ module ActiveRecord
670
685
  subquery_alias = Arel.sql("subquery_for_count", retryable: true)
671
686
  select_value = operation_over_aggregate_column(column_alias, "count", false)
672
687
 
673
- if column_name == :all
674
- relation.unscope(:order).build_subquery(subquery_alias, select_value)
675
- else
676
- relation.build_subquery(subquery_alias, select_value)
677
- end
688
+ relation.build_subquery(subquery_alias, select_value)
678
689
  end
679
690
  end
680
691
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveRecord
6
5
  module Delegation # :nodoc:
@@ -141,7 +141,7 @@ module ActiveRecord
141
141
  #
142
142
  # Product.where(["price = %?", price]).sole
143
143
  def sole
144
- found, undesired = first(2)
144
+ found, undesired = take(2)
145
145
 
146
146
  if found.nil?
147
147
  raise_record_not_found_exception!
@@ -442,7 +442,7 @@ module ActiveRecord
442
442
  if distinct_value && offset_value
443
443
  relation = except(:order).limit!(1)
444
444
  else
445
- relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
445
+ relation = except(:select, :distinct, :order)._select!(Arel.sql(ONE_AS_ONE, retryable: true)).limit!(1)
446
446
  end
447
447
 
448
448
  case conditions
@@ -639,24 +639,40 @@ module ActiveRecord
639
639
  end
640
640
 
641
641
  def ordered_relation
642
- if order_values.empty? && (model.implicit_order_column || !model.query_constraints_list.nil? || primary_key)
643
- order(_order_columns.map { |column| table[column].asc })
642
+ if order_values.empty?
643
+ if !_order_columns.empty?
644
+ return order(_order_columns.map { |column| table[column].asc })
645
+ end
646
+
647
+ if ActiveRecord.raise_on_missing_required_finder_order_columns
648
+ raise MissingRequiredOrderError, <<~MSG.squish
649
+ Relation has no order values, and #{model} has no order columns to use as a default.
650
+ Set at least one of `implicit_order_column`, `query_constraints` or `primary_key` on
651
+ the model when no `order `is specified on the relation.
652
+ MSG
653
+ else
654
+ ActiveRecord.deprecator.warn(<<~MSG)
655
+ Calling order dependent finder methods (e.g. `#first`, `#second`) without `order` values on the relation,
656
+ and on a model (#{model}) that does not have any order columns (`implicit_order_column`, `query_constraints`,
657
+ or `primary_key`) to fall back on is deprecated and will raise `ActiveRecord::MissingRequiredOrderError`
658
+ in Rails 8.2.
659
+ MSG
660
+
661
+ self
662
+ end
644
663
  else
645
664
  self
646
665
  end
647
666
  end
648
667
 
649
668
  def _order_columns
650
- oc = []
669
+ columns = Array(model.implicit_order_column)
651
670
 
652
- oc << model.implicit_order_column if model.implicit_order_column
653
- oc << model.query_constraints_list if model.query_constraints_list
671
+ return columns.compact if columns.length.positive? && columns.last.nil?
654
672
 
655
- if model.primary_key && model.query_constraints_list.nil?
656
- oc << model.primary_key
657
- end
673
+ columns += Array(model.query_constraints_list || model.primary_key)
658
674
 
659
- oc.flatten.uniq.compact
675
+ columns.uniq.compact
660
676
  end
661
677
  end
662
678
  end
@@ -3,24 +3,24 @@
3
3
  module ActiveRecord
4
4
  class PredicateBuilder
5
5
  class AssociationQueryValue # :nodoc:
6
- def initialize(associated_table, value)
7
- @associated_table = associated_table
6
+ def initialize(reflection, value)
7
+ @reflection = reflection
8
8
  @value = value
9
9
  end
10
10
 
11
11
  def queries
12
- if associated_table.join_foreign_key.is_a?(Array)
12
+ if reflection.join_foreign_key.is_a?(Array)
13
13
  id_list = ids
14
14
  id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
15
15
 
16
- id_list.map { |ids_set| associated_table.join_foreign_key.zip(ids_set).to_h }
16
+ id_list.map { |ids_set| reflection.join_foreign_key.zip(ids_set).to_h }
17
17
  else
18
- [ associated_table.join_foreign_key => ids ]
18
+ [ reflection.join_foreign_key => ids ]
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
- attr_reader :associated_table, :value
23
+ attr_reader :reflection, :value
24
24
 
25
25
  def ids
26
26
  case value
@@ -37,15 +37,15 @@ module ActiveRecord
37
37
  end
38
38
 
39
39
  def primary_key
40
- associated_table.join_primary_key
40
+ reflection.join_primary_key
41
41
  end
42
42
 
43
43
  def primary_type
44
- associated_table.join_primary_type
44
+ reflection.join_primary_type
45
45
  end
46
46
 
47
47
  def polymorphic_name
48
- associated_table.polymorphic_name_association
48
+ reflection.polymorphic_name
49
49
  end
50
50
 
51
51
  def select_clause?
@@ -3,24 +3,24 @@
3
3
  module ActiveRecord
4
4
  class PredicateBuilder
5
5
  class PolymorphicArrayValue # :nodoc:
6
- def initialize(associated_table, values)
7
- @associated_table = associated_table
6
+ def initialize(reflection, values)
7
+ @reflection = reflection
8
8
  @values = values
9
9
  end
10
10
 
11
11
  def queries
12
- return [ associated_table.join_foreign_key => values ] if values.empty?
12
+ return [ reflection.join_foreign_key => values ] if values.empty?
13
13
 
14
14
  type_to_ids_mapping.map do |type, ids|
15
15
  query = {}
16
- query[associated_table.join_foreign_type] = type if type
17
- query[associated_table.join_foreign_key] = ids
16
+ query[reflection.join_foreign_type] = type if type
17
+ query[reflection.join_foreign_key] = ids
18
18
  query
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
- attr_reader :associated_table, :values
23
+ attr_reader :reflection, :values
24
24
 
25
25
  def type_to_ids_mapping
26
26
  default_hash = Hash.new { |hsh, key| hsh[key] = [] }
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
  end
31
31
 
32
32
  def primary_key(value)
33
- associated_table.join_primary_key(klass(value))
33
+ reflection.join_primary_key(klass(value))
34
34
  end
35
35
 
36
36
  def klass(value)
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
 
38
38
  # Define how a class is converted to Arel nodes when passed to +where+.
39
39
  # The handler can be any object that responds to +call+, and will be used
40
- # for any value that +===+ the class given. For example:
40
+ # for any value that <tt>===</tt> the class given. For example:
41
41
  #
42
42
  # MyCustomDateRange = Struct.new(:start, :end)
43
43
  # handler = proc do |column, range|
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
  attr_writer :table
83
83
 
84
84
  def expand_from_hash(attributes, &block)
85
- return ["1=0"] if attributes.empty?
85
+ return [Arel.sql("1=0", retryable: true)] if attributes.empty?
86
86
 
87
87
  attributes.flat_map do |key, value|
88
88
  if key.is_a?(Array) && key.size == 1
@@ -99,24 +99,26 @@ module ActiveRecord
99
99
  elsif value.is_a?(Hash) && !table.has_column?(key)
100
100
  table.associated_table(key, &block)
101
101
  .predicate_builder.expand_from_hash(value.stringify_keys)
102
- elsif table.associated_with?(key)
102
+ elsif (associated_reflection = table.associated_with(key))
103
103
  # Find the foreign key when using queries such as:
104
104
  # Post.where(author: author)
105
105
  #
106
106
  # For polymorphic relationships, find the foreign key and type:
107
107
  # PriceEstimate.where(estimate_of: treasure)
108
- associated_table = table.associated_table(key)
109
- if associated_table.polymorphic_association?
108
+
109
+ if associated_reflection.polymorphic?
110
110
  value = [value] unless value.is_a?(Array)
111
111
  klass = PolymorphicArrayValue
112
- elsif associated_table.through_association?
112
+ elsif associated_reflection.through_reflection?
113
+ associated_table = table.associated_table(key)
114
+
113
115
  next associated_table.predicate_builder.expand_from_hash(
114
116
  associated_table.primary_key => value
115
117
  )
116
118
  end
117
119
 
118
120
  klass ||= AssociationQueryValue
119
- queries = klass.new(associated_table, value).queries.map! do |query|
121
+ queries = klass.new(associated_reflection, value).queries.map! do |query|
120
122
  # If the query produced is identical to attributes don't go any deeper.
121
123
  # Prevents stack level too deep errors when association and foreign_key are identical.
122
124
  query == attributes ? self[key, value] : expand_from_hash(query)
@@ -15,7 +15,9 @@ module ActiveRecord
15
15
  elsif @type.serialized?
16
16
  value_for_database
17
17
  elsif @type.mutable? # If the type is simply mutable, we deep_dup it.
18
- @value_before_type_cast = @value_before_type_cast.deep_dup
18
+ unless @value_before_type_cast.frozen?
19
+ @value_before_type_cast = @value_before_type_cast.deep_dup
20
+ end
19
21
  end
20
22
  end
21
23
 
@@ -576,7 +576,7 @@ module ActiveRecord
576
576
  end
577
577
 
578
578
  def group!(*args) # :nodoc:
579
- self.group_values += args
579
+ self.group_values |= args
580
580
  self
581
581
  end
582
582
 
@@ -809,7 +809,7 @@ module ActiveRecord
809
809
  end
810
810
 
811
811
  def unscope!(*args) # :nodoc:
812
- self.unscope_values += args
812
+ self.unscope_values |= args
813
813
 
814
814
  args.each do |scope|
815
815
  case scope
@@ -1213,6 +1213,7 @@ module ActiveRecord
1213
1213
  end
1214
1214
 
1215
1215
  def limit!(value) # :nodoc:
1216
+ value = Integer(value) unless value.nil?
1216
1217
  self.limit_value = value
1217
1218
  self
1218
1219
  end
@@ -1465,7 +1466,7 @@ module ActiveRecord
1465
1466
  modules << Module.new(&block) if block
1466
1467
  modules.flatten!
1467
1468
 
1468
- self.extending_values += modules
1469
+ self.extending_values |= modules
1469
1470
  extend(*extending_values) if extending_values.any?
1470
1471
 
1471
1472
  self
@@ -1533,7 +1534,7 @@ module ActiveRecord
1533
1534
 
1534
1535
  # Like #annotate, but modifies relation in place.
1535
1536
  def annotate!(*args) # :nodoc:
1536
- self.annotate_values += args
1537
+ self.annotate_values |= args
1537
1538
  self
1538
1539
  end
1539
1540
 
@@ -1592,7 +1593,7 @@ module ActiveRecord
1592
1593
 
1593
1594
  # Returns the Arel object associated with the relation.
1594
1595
  def arel(aliases = nil) # :nodoc:
1595
- @arel ||= with_connection { |c| build_arel(c, aliases) }
1596
+ @arel ||= build_arel(aliases)
1596
1597
  end
1597
1598
 
1598
1599
  def construct_join_dependency(associations, join_type) # :nodoc:
@@ -1626,7 +1627,7 @@ module ActiveRecord
1626
1627
  elsif opts.include?("?")
1627
1628
  parts = [build_bound_sql_literal(opts, rest)]
1628
1629
  else
1629
- parts = [model.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1630
+ parts = [Arel.sql(model.sanitize_sql([opts, *rest]))]
1630
1631
  end
1631
1632
  when Hash
1632
1633
  opts = opts.transform_keys do |key|
@@ -1653,13 +1654,12 @@ module ActiveRecord
1653
1654
  end
1654
1655
  alias :build_having_clause :build_where_clause
1655
1656
 
1656
- def async!
1657
+ def async! # :nodoc:
1657
1658
  @async = true
1658
1659
  self
1659
1660
  end
1660
1661
 
1661
- protected
1662
- def arel_columns(columns)
1662
+ def arel_columns(columns) # :nodoc:
1663
1663
  columns.flat_map do |field|
1664
1664
  case field
1665
1665
  when Symbol, String
@@ -1747,32 +1747,27 @@ module ActiveRecord
1747
1747
  raise UnmodifiableRelation if @loaded || @arel
1748
1748
  end
1749
1749
 
1750
- def build_arel(connection, aliases = nil)
1750
+ def build_arel(aliases)
1751
1751
  arel = Arel::SelectManager.new(table)
1752
1752
 
1753
1753
  build_joins(arel.join_sources, aliases)
1754
1754
 
1755
1755
  arel.where(where_clause.ast) unless where_clause.empty?
1756
1756
  arel.having(having_clause.ast) unless having_clause.empty?
1757
- arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1757
+ arel.take(build_cast_value("LIMIT", limit_value)) if limit_value
1758
1758
  arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1759
- arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1759
+ arel.group(*arel_columns(group_values)) unless group_values.empty?
1760
1760
 
1761
1761
  build_order(arel)
1762
1762
  build_with(arel)
1763
1763
  build_select(arel)
1764
1764
 
1765
1765
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1766
+ arel.comment(*annotate_values) unless annotate_values.empty?
1766
1767
  arel.distinct(distinct_value)
1767
1768
  arel.from(build_from) unless from_clause.empty?
1768
1769
  arel.lock(lock_value) if lock_value
1769
1770
 
1770
- unless annotate_values.empty?
1771
- annotates = annotate_values
1772
- annotates = annotates.uniq if annotates.size > 1
1773
- arel.comment(*annotates)
1774
- end
1775
-
1776
1771
  arel
1777
1772
  end
1778
1773
 
@@ -1928,7 +1923,8 @@ module ActiveRecord
1928
1923
 
1929
1924
  def build_with_expression_from_value(value, nested = false)
1930
1925
  case value
1931
- when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1926
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::BoundSqlLiteral
1927
+ Arel::Nodes::Grouping.new(value)
1932
1928
  when ActiveRecord::Relation
1933
1929
  if nested
1934
1930
  value.arel.ast
@@ -1990,7 +1986,7 @@ module ActiveRecord
1990
1986
  def arel_column(field)
1991
1987
  field = field.name if is_symbol = field.is_a?(Symbol)
1992
1988
 
1993
- field = model.attribute_aliases[field] || field.to_s
1989
+ field = model.attribute_aliases[field] || field
1994
1990
  from = from_clause.name || from_clause.value
1995
1991
 
1996
1992
  if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
@@ -2001,8 +1997,10 @@ module ActiveRecord
2001
1997
  yield field
2002
1998
  elsif Arel.arel_node?(field)
2003
1999
  field
2000
+ elsif is_symbol
2001
+ Arel.sql(model.adapter_class.quote_table_name(field), retryable: true)
2004
2002
  else
2005
- Arel.sql(is_symbol ? model.adapter_class.quote_table_name(field) : field)
2003
+ Arel.sql(field)
2006
2004
  end
2007
2005
  end
2008
2006
 
@@ -2014,9 +2012,15 @@ module ActiveRecord
2014
2012
 
2015
2013
  def reverse_sql_order(order_query)
2016
2014
  if order_query.empty?
2017
- return [table[primary_key].desc] if primary_key
2018
- raise IrreversibleOrderError,
2019
- "Relation has no current order and table has no primary key to be used as default order"
2015
+ if !_reverse_order_columns.empty?
2016
+ return _reverse_order_columns.map { |column| table[column].desc }
2017
+ end
2018
+
2019
+ raise IrreversibleOrderError, <<~MSG.squish
2020
+ Relation has no order values, and #{model} has no order columns to use as a default.
2021
+ Set at least one of `implicit_order_column`, or `primary_key` on the model when no
2022
+ `order `is specified on the relation.
2023
+ MSG
2020
2024
  end
2021
2025
 
2022
2026
  order_query.flat_map do |o|
@@ -2041,6 +2045,13 @@ module ActiveRecord
2041
2045
  end
2042
2046
  end
2043
2047
 
2048
+ def _reverse_order_columns
2049
+ roc = []
2050
+ roc << model.implicit_order_column if model.implicit_order_column
2051
+ roc << model.primary_key if model.primary_key
2052
+ roc.flatten.uniq.compact
2053
+ end
2054
+
2044
2055
  def does_not_support_reverse?(order)
2045
2056
  # Account for String subclasses like Arel::Nodes::SqlLiteral that
2046
2057
  # override methods like #count.
@@ -2267,11 +2278,11 @@ module ActiveRecord
2267
2278
  values = other.values
2268
2279
  STRUCTURAL_VALUE_METHODS.reject do |method|
2269
2280
  v1, v2 = @values[method], values[method]
2270
- if v1.is_a?(Array)
2271
- next true unless v2.is_a?(Array)
2272
- v1 = v1.uniq
2273
- v2 = v2.uniq
2274
- end
2281
+
2282
+ # `and`/`or` are focused to combine where-like clauses, so it relaxes
2283
+ # the difference when other's multi values are uninitialized.
2284
+ next true if v1.is_a?(Array) && v2.nil?
2285
+
2275
2286
  v1 == v2
2276
2287
  end
2277
2288
  end
@@ -194,7 +194,7 @@ module ActiveRecord
194
194
  non_empty_predicates.map do |node|
195
195
  case node
196
196
  when Arel::Nodes::SqlLiteral, ::String
197
- wrap_sql_literal(node)
197
+ Arel::Nodes::Grouping.new(node)
198
198
  else node
199
199
  end
200
200
  end
@@ -205,13 +205,6 @@ module ActiveRecord
205
205
  predicates - ARRAY_WITH_EMPTY_STRING
206
206
  end
207
207
 
208
- def wrap_sql_literal(node)
209
- if ::String === node
210
- node = Arel.sql(node)
211
- end
212
- Arel::Nodes::Grouping.new(node)
213
- end
214
-
215
208
  def extract_node_value(node)
216
209
  if node.respond_to?(:value_before_type_cast)
217
210
  node.value_before_type_cast