activerecord 8.0.3 → 8.1.0.rc1

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 +520 -514
  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 +66 -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 +54 -30
  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 +2 -1
  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 +3 -3
  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 +42 -3
  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 +38 -28
  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
@@ -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
 
@@ -1990,7 +1985,7 @@ module ActiveRecord
1990
1985
  def arel_column(field)
1991
1986
  field = field.name if is_symbol = field.is_a?(Symbol)
1992
1987
 
1993
- field = model.attribute_aliases[field] || field.to_s
1988
+ field = model.attribute_aliases[field] || field
1994
1989
  from = from_clause.name || from_clause.value
1995
1990
 
1996
1991
  if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
@@ -2001,8 +1996,10 @@ module ActiveRecord
2001
1996
  yield field
2002
1997
  elsif Arel.arel_node?(field)
2003
1998
  field
1999
+ elsif is_symbol
2000
+ Arel.sql(model.adapter_class.quote_table_name(field), retryable: true)
2004
2001
  else
2005
- Arel.sql(is_symbol ? model.adapter_class.quote_table_name(field) : field)
2002
+ Arel.sql(field)
2006
2003
  end
2007
2004
  end
2008
2005
 
@@ -2014,9 +2011,15 @@ module ActiveRecord
2014
2011
 
2015
2012
  def reverse_sql_order(order_query)
2016
2013
  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"
2014
+ if !_reverse_order_columns.empty?
2015
+ return _reverse_order_columns.map { |column| table[column].desc }
2016
+ end
2017
+
2018
+ raise IrreversibleOrderError, <<~MSG.squish
2019
+ Relation has no order values, and #{model} has no order columns to use as a default.
2020
+ Set at least one of `implicit_order_column`, or `primary_key` on the model when no
2021
+ `order `is specified on the relation.
2022
+ MSG
2020
2023
  end
2021
2024
 
2022
2025
  order_query.flat_map do |o|
@@ -2041,6 +2044,13 @@ module ActiveRecord
2041
2044
  end
2042
2045
  end
2043
2046
 
2047
+ def _reverse_order_columns
2048
+ roc = []
2049
+ roc << model.implicit_order_column if model.implicit_order_column
2050
+ roc << model.primary_key if model.primary_key
2051
+ roc.flatten.uniq.compact
2052
+ end
2053
+
2044
2054
  def does_not_support_reverse?(order)
2045
2055
  # Account for String subclasses like Arel::Nodes::SqlLiteral that
2046
2056
  # override methods like #count.
@@ -2267,11 +2277,11 @@ module ActiveRecord
2267
2277
  values = other.values
2268
2278
  STRUCTURAL_VALUE_METHODS.reject do |method|
2269
2279
  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
2280
+
2281
+ # `and`/`or` are focused to combine where-like clauses, so it relaxes
2282
+ # the difference when other's multi values are uninitialized.
2283
+ next true if v1.is_a?(Array) && v2.nil?
2284
+
2275
2285
  v1 == v2
2276
2286
  end
2277
2287
  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
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
  :reverse_order, :distinct, :create_with, :skip_query_cache]
61
61
 
62
62
  CLAUSE_METHODS = [:where, :having, :from]
63
- INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :with, :with_recursive]
63
+ INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL = [:distinct, :with, :with_recursive]
64
64
 
65
65
  VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
66
66
 
@@ -600,6 +600,18 @@ module ActiveRecord
600
600
 
601
601
  return 0 if @none
602
602
 
603
+ invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
604
+ value = @values[method]
605
+ method == :distinct ? value : value&.any?
606
+ end
607
+ if invalid_methods.any?
608
+ ActiveRecord.deprecator.warn <<~MESSAGE
609
+ `#{invalid_methods.join(', ')}` is not supported by `update_all` and was never included in the generated query.
610
+
611
+ Calling `#{invalid_methods.join(', ')}` with `update_all` will raise an error in Rails 8.2.
612
+ MESSAGE
613
+ end
614
+
603
615
  if updates.is_a?(Hash)
604
616
  if model.locking_enabled? &&
605
617
  !updates.key?(model.locking_column) &&
@@ -613,17 +625,15 @@ module ActiveRecord
613
625
  end
614
626
 
615
627
  model.with_connection do |c|
616
- arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
628
+ arel = eager_loading? ? apply_join_dependency.arel : arel()
617
629
  arel.source.left = table
618
630
 
619
- group_values_arel_columns = arel_columns(group_values.uniq)
620
- having_clause_ast = having_clause.ast unless having_clause.empty?
621
631
  key = if model.composite_primary_key?
622
632
  primary_key.map { |pk| table[pk] }
623
633
  else
624
634
  table[primary_key]
625
635
  end
626
- stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
636
+ stmt = arel.compile_update(values, key)
627
637
  c.update(stmt, "#{model} Update All").tap { reset }
628
638
  end
629
639
  end
@@ -1021,7 +1031,7 @@ module ActiveRecord
1021
1031
  def delete_all
1022
1032
  return 0 if @none
1023
1033
 
1024
- invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
1034
+ invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
1025
1035
  value = @values[method]
1026
1036
  method == :distinct ? value : value&.any?
1027
1037
  end
@@ -1030,17 +1040,15 @@ module ActiveRecord
1030
1040
  end
1031
1041
 
1032
1042
  model.with_connection do |c|
1033
- arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
1043
+ arel = eager_loading? ? apply_join_dependency.arel : arel()
1034
1044
  arel.source.left = table
1035
1045
 
1036
- group_values_arel_columns = arel_columns(group_values.uniq)
1037
- having_clause_ast = having_clause.ast unless having_clause.empty?
1038
1046
  key = if model.composite_primary_key?
1039
1047
  primary_key.map { |pk| table[pk] }
1040
1048
  else
1041
1049
  table[primary_key]
1042
1050
  end
1043
- stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
1051
+ stmt = arel.compile_delete(key)
1044
1052
 
1045
1053
  c.delete(stmt, "#{model} Delete All").tap { reset }
1046
1054
  end
@@ -1405,12 +1413,16 @@ module ActiveRecord
1405
1413
 
1406
1414
  def _increment_attribute(attribute, value = 1)
1407
1415
  bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
1408
- expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
1416
+ expr = table.coalesce(attribute, 0)
1409
1417
  expr = value < 0 ? expr - bind : expr + bind
1410
1418
  expr.expr
1411
1419
  end
1412
1420
 
1413
1421
  def exec_queries(&block)
1422
+ if lock_value && model.current_preventing_writes
1423
+ raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
1424
+ end
1425
+
1414
1426
  skip_query_cache_if_necessary do
1415
1427
  rows = if scheduled?
1416
1428
  future = @future_result
@@ -1450,7 +1462,7 @@ module ActiveRecord
1450
1462
  else
1451
1463
  relation = join_dependency.apply_column_aliases(relation)
1452
1464
  @_join_dependency = join_dependency
1453
- c.select_all(relation.arel, "SQL", async: async)
1465
+ c.select_all(relation.arel, "#{model.name} Eager Load", async: async)
1454
1466
  end
1455
1467
  end
1456
1468
  end
@@ -29,6 +29,11 @@ module ActiveRecord
29
29
  # ...
30
30
  # ]
31
31
  #
32
+ # # Get the number of rows affected by the query:
33
+ # result = ActiveRecord::Base.lease_connection.exec_query('INSERT INTO posts (title, body) VALUES ("title_3", "body_3"), ("title_4", "body_4")')
34
+ # result.affected_rows
35
+ # # => 2
36
+ #
32
37
  # # ActiveRecord::Result also includes Enumerable.
33
38
  # result.each do |row|
34
39
  # puts row['title'] + " " + row['body']
@@ -89,24 +94,26 @@ module ActiveRecord
89
94
  alias_method :to_hash, :to_h
90
95
  end
91
96
 
92
- attr_reader :columns, :rows, :column_types
97
+ attr_reader :columns, :rows, :affected_rows
93
98
 
94
- def self.empty(async: false) # :nodoc:
99
+ def self.empty(async: false, affected_rows: nil) # :nodoc:
95
100
  if async
96
- EMPTY_ASYNC
101
+ FutureResult.wrap(new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows)).freeze
97
102
  else
98
- EMPTY
103
+ new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows).freeze
99
104
  end
100
105
  end
101
106
 
102
- def initialize(columns, rows, column_types = nil)
107
+ def initialize(columns, rows, column_types = nil, affected_rows: nil)
103
108
  # We freeze the strings to prevent them getting duped when
104
109
  # used as keys in ActiveRecord::Base's @attributes hash
105
110
  @columns = columns.each(&:-@).freeze
106
111
  @rows = rows
107
112
  @hash_rows = nil
108
- @column_types = column_types || EMPTY_HASH
113
+ @column_types = column_types.freeze
114
+ @types_hash = nil
109
115
  @column_indexes = nil
116
+ @affected_rows = affected_rows
110
117
  end
111
118
 
112
119
  # Returns true if this result set includes the column named +name+
@@ -154,6 +161,24 @@ module ActiveRecord
154
161
  n ? hash_rows.last(n) : hash_rows.last
155
162
  end
156
163
 
164
+ # Returns the +ActiveRecord::Type+ type of all columns.
165
+ # Note that not all database adapters return the result types,
166
+ # so the hash may be empty.
167
+ def column_types
168
+ if @column_types
169
+ @types_hash ||= begin
170
+ types = {}
171
+ @columns.each_with_index do |name, index|
172
+ type = @column_types[index] || Type.default_value
173
+ types[name] = types[index] = type
174
+ end
175
+ types.freeze
176
+ end
177
+ else
178
+ EMPTY_HASH
179
+ end
180
+ end
181
+
157
182
  def result # :nodoc:
158
183
  self
159
184
  end
@@ -162,7 +187,7 @@ module ActiveRecord
162
187
  self
163
188
  end
164
189
 
165
- def cast_values(type_overrides = {}) # :nodoc:
190
+ def cast_values(type_overrides = nil) # :nodoc:
166
191
  if columns.one?
167
192
  # Separated to avoid allocating an array per row
168
193
 
@@ -190,13 +215,13 @@ module ActiveRecord
190
215
 
191
216
  def initialize_copy(other)
192
217
  @rows = rows.dup
193
- @column_types = column_types.dup
194
218
  @hash_rows = nil
195
219
  end
196
220
 
197
221
  def freeze # :nodoc:
198
222
  hash_rows.freeze
199
- indexed_rows.freeze
223
+ indexed_rows
224
+ column_types
200
225
  super
201
226
  end
202
227
 
@@ -204,7 +229,7 @@ module ActiveRecord
204
229
  @column_indexes ||= begin
205
230
  index = 0
206
231
  hash = {}
207
- length = columns.length
232
+ length = columns.length
208
233
  while index < length
209
234
  hash[columns[index]] = index
210
235
  index += 1
@@ -222,10 +247,14 @@ module ActiveRecord
222
247
 
223
248
  private
224
249
  def column_type(name, index, type_overrides)
225
- type_overrides.fetch(name) do
226
- column_types.fetch(index) do
227
- column_types.fetch(name, Type.default_value)
250
+ if type_overrides
251
+ type_overrides.fetch(name) do
252
+ column_type(name, index, nil)
228
253
  end
254
+ elsif @column_types
255
+ @column_types[index] || Type.default_value
256
+ else
257
+ Type.default_value
229
258
  end
230
259
  end
231
260
 
@@ -237,14 +266,8 @@ module ActiveRecord
237
266
  end
238
267
  end
239
268
 
240
- empty_array = [].freeze
269
+ EMPTY_ARRAY = [].freeze
241
270
  EMPTY_HASH = {}.freeze
242
- private_constant :EMPTY_HASH
243
-
244
- EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
245
- private_constant :EMPTY
246
-
247
- EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
248
- private_constant :EMPTY_ASYNC
271
+ private_constant :EMPTY_ARRAY, :EMPTY_HASH
249
272
  end
250
273
  end