activerecord 7.0.4 → 7.1.5.1

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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1971 -1243
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +20 -14
  15. data/lib/active_record/associations/collection_proxy.rb +20 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader.rb +13 -10
  25. data/lib/active_record/associations/singular_association.rb +1 -1
  26. data/lib/active_record/associations/through_association.rb +22 -11
  27. data/lib/active_record/associations.rb +333 -222
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  31. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  32. data/lib/active_record/attribute_methods/query.rb +28 -16
  33. data/lib/active_record/attribute_methods/read.rb +21 -8
  34. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
  36. data/lib/active_record/attribute_methods/write.rb +6 -6
  37. data/lib/active_record/attribute_methods.rb +148 -26
  38. data/lib/active_record/attributes.rb +3 -3
  39. data/lib/active_record/autosave_association.rb +59 -10
  40. data/lib/active_record/base.rb +7 -2
  41. data/lib/active_record/callbacks.rb +16 -32
  42. data/lib/active_record/coders/column_serializer.rb +61 -0
  43. data/lib/active_record/coders/json.rb +1 -1
  44. data/lib/active_record/coders/yaml_column.rb +70 -42
  45. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  47. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +155 -25
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
  60. data/lib/active_record/connection_adapters/column.rb +9 -0
  61. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  71. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  81. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  82. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  83. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +365 -61
  85. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
  87. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  88. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  91. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
  94. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  97. data/lib/active_record/connection_adapters.rb +3 -1
  98. data/lib/active_record/connection_handling.rb +72 -95
  99. data/lib/active_record/core.rb +181 -154
  100. data/lib/active_record/counter_cache.rb +52 -27
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  102. data/lib/active_record/database_configurations/database_config.rb +9 -3
  103. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  104. data/lib/active_record/database_configurations/url_config.rb +17 -11
  105. data/lib/active_record/database_configurations.rb +86 -33
  106. data/lib/active_record/delegated_type.rb +15 -10
  107. data/lib/active_record/deprecator.rb +7 -0
  108. data/lib/active_record/destroy_association_async_job.rb +3 -1
  109. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  110. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  111. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  112. data/lib/active_record/encryption/config.rb +25 -1
  113. data/lib/active_record/encryption/configurable.rb +12 -19
  114. data/lib/active_record/encryption/context.rb +10 -3
  115. data/lib/active_record/encryption/contexts.rb +5 -1
  116. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  117. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  121. data/lib/active_record/encryption/key_generator.rb +12 -1
  122. data/lib/active_record/encryption/message_serializer.rb +2 -0
  123. data/lib/active_record/encryption/properties.rb +3 -3
  124. data/lib/active_record/encryption/scheme.rb +22 -21
  125. data/lib/active_record/encryption.rb +3 -0
  126. data/lib/active_record/enum.rb +112 -28
  127. data/lib/active_record/errors.rb +112 -18
  128. data/lib/active_record/explain.rb +23 -3
  129. data/lib/active_record/explain_subscriber.rb +1 -1
  130. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  131. data/lib/active_record/fixture_set/render_context.rb +2 -0
  132. data/lib/active_record/fixture_set/table_row.rb +29 -8
  133. data/lib/active_record/fixtures.rb +135 -71
  134. data/lib/active_record/future_result.rb +40 -5
  135. data/lib/active_record/gem_version.rb +4 -4
  136. data/lib/active_record/inheritance.rb +30 -16
  137. data/lib/active_record/insert_all.rb +57 -10
  138. data/lib/active_record/integration.rb +8 -8
  139. data/lib/active_record/internal_metadata.rb +120 -30
  140. data/lib/active_record/locking/optimistic.rb +33 -19
  141. data/lib/active_record/locking/pessimistic.rb +5 -2
  142. data/lib/active_record/log_subscriber.rb +29 -12
  143. data/lib/active_record/marshalling.rb +59 -0
  144. data/lib/active_record/message_pack.rb +124 -0
  145. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  146. data/lib/active_record/middleware/database_selector.rb +9 -11
  147. data/lib/active_record/middleware/shard_selector.rb +3 -1
  148. data/lib/active_record/migration/command_recorder.rb +105 -7
  149. data/lib/active_record/migration/compatibility.rb +163 -58
  150. data/lib/active_record/migration/default_strategy.rb +23 -0
  151. data/lib/active_record/migration/execution_strategy.rb +19 -0
  152. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  153. data/lib/active_record/migration.rb +271 -114
  154. data/lib/active_record/model_schema.rb +69 -44
  155. data/lib/active_record/nested_attributes.rb +37 -8
  156. data/lib/active_record/normalization.rb +167 -0
  157. data/lib/active_record/persistence.rb +195 -42
  158. data/lib/active_record/promise.rb +84 -0
  159. data/lib/active_record/query_cache.rb +4 -22
  160. data/lib/active_record/query_logs.rb +87 -51
  161. data/lib/active_record/query_logs_formatter.rb +41 -0
  162. data/lib/active_record/querying.rb +15 -2
  163. data/lib/active_record/railtie.rb +107 -45
  164. data/lib/active_record/railties/controller_runtime.rb +14 -9
  165. data/lib/active_record/railties/databases.rake +144 -150
  166. data/lib/active_record/railties/job_runtime.rb +23 -0
  167. data/lib/active_record/readonly_attributes.rb +32 -5
  168. data/lib/active_record/reflection.rb +189 -45
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  170. data/lib/active_record/relation/batches.rb +190 -61
  171. data/lib/active_record/relation/calculations.rb +232 -81
  172. data/lib/active_record/relation/delegation.rb +23 -9
  173. data/lib/active_record/relation/finder_methods.rb +77 -16
  174. data/lib/active_record/relation/merger.rb +2 -0
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  178. data/lib/active_record/relation/predicate_builder.rb +26 -14
  179. data/lib/active_record/relation/query_attribute.rb +25 -1
  180. data/lib/active_record/relation/query_methods.rb +408 -76
  181. data/lib/active_record/relation/spawn_methods.rb +18 -1
  182. data/lib/active_record/relation.rb +103 -37
  183. data/lib/active_record/result.rb +25 -9
  184. data/lib/active_record/runtime_registry.rb +24 -1
  185. data/lib/active_record/sanitization.rb +51 -11
  186. data/lib/active_record/schema.rb +2 -3
  187. data/lib/active_record/schema_dumper.rb +50 -7
  188. data/lib/active_record/schema_migration.rb +68 -33
  189. data/lib/active_record/scoping/default.rb +15 -5
  190. data/lib/active_record/scoping/named.rb +2 -2
  191. data/lib/active_record/scoping.rb +2 -1
  192. data/lib/active_record/secure_password.rb +60 -0
  193. data/lib/active_record/secure_token.rb +21 -3
  194. data/lib/active_record/signed_id.rb +7 -5
  195. data/lib/active_record/store.rb +9 -9
  196. data/lib/active_record/suppressor.rb +3 -1
  197. data/lib/active_record/table_metadata.rb +16 -3
  198. data/lib/active_record/tasks/database_tasks.rb +152 -108
  199. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  200. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  201. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  202. data/lib/active_record/test_fixtures.rb +114 -96
  203. data/lib/active_record/timestamp.rb +30 -16
  204. data/lib/active_record/token_for.rb +113 -0
  205. data/lib/active_record/touch_later.rb +11 -6
  206. data/lib/active_record/transactions.rb +39 -13
  207. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  208. data/lib/active_record/type/internal/timezone.rb +7 -2
  209. data/lib/active_record/type/serialized.rb +8 -4
  210. data/lib/active_record/type/time.rb +4 -0
  211. data/lib/active_record/validations/absence.rb +1 -1
  212. data/lib/active_record/validations/numericality.rb +5 -4
  213. data/lib/active_record/validations/presence.rb +5 -28
  214. data/lib/active_record/validations/uniqueness.rb +47 -2
  215. data/lib/active_record/validations.rb +8 -4
  216. data/lib/active_record/version.rb +1 -1
  217. data/lib/active_record.rb +130 -17
  218. data/lib/arel/errors.rb +10 -0
  219. data/lib/arel/factory_methods.rb +4 -0
  220. data/lib/arel/filter_predications.rb +1 -1
  221. data/lib/arel/nodes/and.rb +4 -0
  222. data/lib/arel/nodes/binary.rb +6 -1
  223. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  224. data/lib/arel/nodes/cte.rb +36 -0
  225. data/lib/arel/nodes/filter.rb +1 -1
  226. data/lib/arel/nodes/fragments.rb +35 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  228. data/lib/arel/nodes/leading_join.rb +8 -0
  229. data/lib/arel/nodes/node.rb +111 -2
  230. data/lib/arel/nodes/sql_literal.rb +6 -0
  231. data/lib/arel/nodes/table_alias.rb +4 -0
  232. data/lib/arel/nodes.rb +4 -0
  233. data/lib/arel/predications.rb +2 -0
  234. data/lib/arel/table.rb +9 -5
  235. data/lib/arel/tree_manager.rb +5 -1
  236. data/lib/arel/visitors/mysql.rb +8 -1
  237. data/lib/arel/visitors/to_sql.rb +83 -18
  238. data/lib/arel/visitors/visitor.rb +2 -2
  239. data/lib/arel.rb +16 -2
  240. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  241. data/lib/rails/generators/active_record/migration.rb +3 -1
  242. data/lib/rails/generators/active_record/model/USAGE +113 -0
  243. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  244. metadata +51 -15
  245. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  246. data/lib/active_record/null_relation.rb +0 -63
@@ -4,7 +4,7 @@ require "active_support/inflector"
4
4
  require "active_support/core_ext/hash/indifferent_access"
5
5
 
6
6
  module ActiveRecord
7
- # == Single table inheritance
7
+ # = Single table inheritance
8
8
  #
9
9
  # Active Record allows inheritance by storing the name of the class in a column that by
10
10
  # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
@@ -32,8 +32,9 @@ module ActiveRecord
32
32
  # be triggered. In that case, it'll work just like normal subclasses with no special magic
33
33
  # for differentiating between them or reloading the right type with find.
34
34
  #
35
- # Note, all the attributes for all the cases are kept in the same table. Read more:
36
- # https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
35
+ # Note, all the attributes for all the cases are kept in the same table.
36
+ # Read more:
37
+ # * https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
37
38
  #
38
39
  module Inheritance
39
40
  extend ActiveSupport::Concern
@@ -93,14 +94,24 @@ module ActiveRecord
93
94
  :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
94
95
  end
95
96
 
96
- # Returns the class descending directly from ActiveRecord::Base, or
97
- # an abstract class, if any, in the inheritance hierarchy.
97
+ # Returns the first class in the inheritance hierarchy that descends from either an
98
+ # abstract class or from <tt>ActiveRecord::Base</tt>.
98
99
  #
99
- # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A
100
- # through some arbitrarily deep hierarchy, B.base_class will return A.
100
+ # Consider the following behaviour:
101
101
  #
102
- # If B < A and C < B and if A is an abstract_class then both B.base_class
103
- # and C.base_class would return B as the answer since A is an abstract_class.
102
+ # class ApplicationRecord < ActiveRecord::Base
103
+ # self.abstract_class = true
104
+ # end
105
+ # class Shape < ApplicationRecord
106
+ # self.abstract_class = true
107
+ # end
108
+ # Polygon = Class.new(Shape)
109
+ # Square = Class.new(Polygon)
110
+ #
111
+ # ApplicationRecord.base_class # => ApplicationRecord
112
+ # Shape.base_class # => Shape
113
+ # Polygon.base_class # => Polygon
114
+ # Square.base_class # => Polygon
104
115
  attr_reader :base_class
105
116
 
106
117
  # Returns whether the class is a base class.
@@ -116,7 +127,7 @@ module ActiveRecord
116
127
  # true.
117
128
  # +ApplicationRecord+, for example, is generated as an abstract class.
118
129
  #
119
- # Consider the following default behaviour:
130
+ # Consider the following default behavior:
120
131
  #
121
132
  # Shape = Class.new(ActiveRecord::Base)
122
133
  # Polygon = Class.new(Shape)
@@ -210,12 +221,6 @@ module ActiveRecord
210
221
  end
211
222
  end
212
223
 
213
- def inherited(subclass)
214
- subclass.set_base_class
215
- subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
216
- super
217
- end
218
-
219
224
  def dup # :nodoc:
220
225
  # `initialize_dup` / `initialize_copy` don't work when defined
221
226
  # in the `singleton_class`.
@@ -277,6 +282,15 @@ module ActiveRecord
277
282
  end
278
283
 
279
284
  private
285
+ def inherited(subclass)
286
+ super
287
+ subclass.set_base_class
288
+ subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
289
+ subclass.class_eval do
290
+ @finder_needs_type_condition = nil
291
+ end
292
+ end
293
+
280
294
  # Called by +instantiate+ to decide which class to use for a new
281
295
  # record instance. For single-table inheritance, we check the record
282
296
  # for a +type+ column and return the corresponding class.
@@ -8,16 +8,20 @@ module ActiveRecord
8
8
  attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
9
9
 
10
10
  def initialize(model, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
11
- raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
12
-
13
- @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
11
+ @model, @connection, @inserts = model, model.connection, inserts.map(&:stringify_keys)
14
12
  @on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
15
13
  @record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
16
14
 
17
15
  disallow_raw_sql!(on_duplicate)
18
16
  disallow_raw_sql!(returning)
19
17
 
20
- configure_on_duplicate_update_logic
18
+ if @inserts.empty?
19
+ @keys = []
20
+ else
21
+ resolve_sti
22
+ resolve_attribute_aliases
23
+ @keys = @inserts.first.keys
24
+ end
21
25
 
22
26
  if model.scope_attributes?
23
27
  @scope_attributes = model.scope_attributes
@@ -28,13 +32,15 @@ module ActiveRecord
28
32
  @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
29
33
  @returning = false if @returning == []
30
34
 
31
- @unique_by = find_unique_index_for(unique_by)
32
- @on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
35
+ @unique_by = find_unique_index_for(@unique_by)
33
36
 
37
+ configure_on_duplicate_update_logic
34
38
  ensure_valid_options_for_connection!
35
39
  end
36
40
 
37
41
  def execute
42
+ return ActiveRecord::Result.empty if inserts.empty?
43
+
38
44
  message = +"#{model} "
39
45
  message << "Bulk " if inserts.many?
40
46
  message << (on_duplicate == :update ? "Upsert" : "Insert")
@@ -76,7 +82,7 @@ module ActiveRecord
76
82
  @record_timestamps
77
83
  end
78
84
 
79
- # TODO: Consider remaining this method, as it only conditionally extends keys, not always
85
+ # TODO: Consider renaming this method, as it only conditionally extends keys, not always
80
86
  def keys_including_timestamps
81
87
  @keys_including_timestamps ||= if record_timestamps?
82
88
  keys + model.all_timestamp_attributes_in_model
@@ -88,6 +94,34 @@ module ActiveRecord
88
94
  private
89
95
  attr_reader :scope_attributes
90
96
 
97
+ def has_attribute_aliases?(attributes)
98
+ attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
99
+ end
100
+
101
+ def resolve_sti
102
+ return if model.descends_from_active_record?
103
+
104
+ sti_type = model.sti_name
105
+ @inserts = @inserts.map do |insert|
106
+ insert.reverse_merge(model.inheritance_column.to_s => sti_type)
107
+ end
108
+ end
109
+
110
+ def resolve_attribute_aliases
111
+ return unless has_attribute_aliases?(@inserts.first)
112
+
113
+ @inserts = @inserts.map do |insert|
114
+ insert.transform_keys { |attribute| resolve_attribute_alias(attribute) }
115
+ end
116
+
117
+ @update_only = Array(@update_only).map { |attribute| resolve_attribute_alias(attribute) } if @update_only
118
+ @unique_by = Array(@unique_by).map { |attribute| resolve_attribute_alias(attribute) } if @unique_by
119
+ end
120
+
121
+ def resolve_attribute_alias(attribute)
122
+ model.attribute_alias(attribute) || attribute
123
+ end
124
+
91
125
  def configure_on_duplicate_update_logic
92
126
  if custom_update_sql_provided? && update_only.present?
93
127
  raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time"
@@ -99,6 +133,8 @@ module ActiveRecord
99
133
  elsif custom_update_sql_provided?
100
134
  @update_sql = on_duplicate
101
135
  @on_duplicate = :update
136
+ elsif @on_duplicate == :update && updatable_columns.empty?
137
+ @on_duplicate = :skip
102
138
  end
103
139
  end
104
140
 
@@ -115,8 +151,9 @@ module ActiveRecord
115
151
 
116
152
  name_or_columns = unique_by || model.primary_key
117
153
  match = Array(name_or_columns).map(&:to_s)
154
+ sorted_match = match.sort
118
155
 
119
- if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == match }
156
+ if index = unique_indexes.find { |i| match.include?(i.name) || Array(i.columns).sort == sorted_match }
120
157
  index
121
158
  elsif match == primary_keys
122
159
  unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match)
@@ -212,7 +249,13 @@ module ActiveRecord
212
249
  if insert_all.returning.is_a?(String)
213
250
  insert_all.returning
214
251
  else
215
- format_columns(insert_all.returning)
252
+ Array(insert_all.returning).map do |attribute|
253
+ if model.attribute_alias?(attribute)
254
+ "#{quote_column(model.attribute_alias(attribute))} AS #{quote_column(attribute)}"
255
+ else
256
+ quote_column(attribute)
257
+ end
258
+ end.join(",")
216
259
  end
217
260
  end
218
261
 
@@ -271,7 +314,11 @@ module ActiveRecord
271
314
  end
272
315
 
273
316
  def quote_columns(columns)
274
- columns.map(&connection.method(:quote_column_name))
317
+ columns.map { |column| quote_column(column) }
318
+ end
319
+
320
+ def quote_column(column)
321
+ connection.quote_column_name(column)
275
322
  end
276
323
  end
277
324
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  ##
11
11
  # :singleton-method:
12
12
  # Indicates the format used to generate the timestamp in the cache key, if
13
- # versioning is off. Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
13
+ # versioning is off. Accepts any of the symbols in +Time::DATE_FORMATS+.
14
14
  #
15
15
  # This is +:usec+, by default.
16
16
  class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  # Indicates whether to use a stable #cache_key method that is accompanied
21
21
  # by a changing version in the #cache_version method.
22
22
  #
23
- # This is +true+, by default on Rails 5.2 and above.
23
+ # This is +true+, by default on \Rails 5.2 and above.
24
24
  class_attribute :cache_versioning, instance_writer: false, default: false
25
25
 
26
26
  ##
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  # Indicates whether to use a stable #cache_key method that is accompanied
29
29
  # by a changing version in the #cache_version method on collections.
30
30
  #
31
- # This is +false+, by default until Rails 6.1.
31
+ # This is +false+, by default until \Rails 6.1.
32
32
  class_attribute :collection_cache_versioning, instance_writer: false, default: false
33
33
  end
34
34
 
@@ -55,8 +55,8 @@ module ActiveRecord
55
55
  # user = User.find_by(name: 'Phusion')
56
56
  # user_path(user) # => "/users/Phusion"
57
57
  def to_param
58
- # We can't use alias_method here, because method 'id' optimizes itself on the fly.
59
- id && id.to_s # Be sure to stringify the id for routes
58
+ return unless id
59
+ Array(id).join(self.class.param_delimiter)
60
60
  end
61
61
 
62
62
  # Returns a stable cache key that can be used to identify this record.
@@ -64,7 +64,7 @@ module ActiveRecord
64
64
  # Product.new.cache_key # => "products/new"
65
65
  # Product.find(5).cache_key # => "products/5"
66
66
  #
67
- # If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
67
+ # If ActiveRecord::Base.cache_versioning is turned off, as it was in \Rails 5.1 and earlier,
68
68
  # the cache key will also include a version.
69
69
  #
70
70
  # Product.cache_versioning = false
@@ -106,7 +106,7 @@ module ActiveRecord
106
106
  timestamp.utc.to_fs(cache_timestamp_format)
107
107
  end
108
108
  elsif self.class.has_attribute?("updated_at")
109
- raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
109
+ raise ActiveModel::MissingAttributeError, "missing attribute 'updated_at' for #{self.class}"
110
110
  end
111
111
  end
112
112
 
@@ -178,7 +178,7 @@ module ActiveRecord
178
178
  def can_use_fast_cache_version?(timestamp)
179
179
  timestamp.is_a?(String) &&
180
180
  cache_timestamp_format == :usec &&
181
- ActiveRecord.default_timezone == :utc &&
181
+ self.class.connection.default_timezone == :utc &&
182
182
  !updated_at_came_from_user?
183
183
  end
184
184
 
@@ -9,52 +9,142 @@ module ActiveRecord
9
9
  #
10
10
  # This is enabled by default. To disable this functionality set
11
11
  # `use_metadata_table` to false in your database configuration.
12
- class InternalMetadata < ActiveRecord::Base # :nodoc:
13
- self.record_timestamps = true
12
+ class InternalMetadata # :nodoc:
13
+ class NullInternalMetadata # :nodoc:
14
+ end
14
15
 
15
- class << self
16
- def enabled?
17
- ActiveRecord::Base.connection.use_metadata_table?
18
- end
16
+ attr_reader :connection, :arel_table
19
17
 
20
- def primary_key
21
- "key"
22
- end
18
+ def initialize(connection)
19
+ @connection = connection
20
+ @arel_table = Arel::Table.new(table_name)
21
+ end
23
22
 
24
- def table_name
25
- "#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}"
26
- end
23
+ def enabled?
24
+ connection.use_metadata_table?
25
+ end
27
26
 
28
- def []=(key, value)
29
- return unless enabled?
27
+ def primary_key
28
+ "key"
29
+ end
30
+
31
+ def value_key
32
+ "value"
33
+ end
34
+
35
+ def table_name
36
+ "#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
37
+ end
30
38
 
31
- find_or_initialize_by(key: key).update!(value: value)
39
+ def []=(key, value)
40
+ return unless enabled?
41
+
42
+ update_or_create_entry(key, value)
43
+ end
44
+
45
+ def [](key)
46
+ return unless enabled?
47
+
48
+ if entry = select_entry(key)
49
+ entry[value_key]
32
50
  end
51
+ end
52
+
53
+ def delete_all_entries
54
+ dm = Arel::DeleteManager.new(arel_table)
55
+
56
+ connection.delete(dm, "#{self.class} Destroy")
57
+ end
58
+
59
+ def count
60
+ sm = Arel::SelectManager.new(arel_table)
61
+ sm.project(*Arel::Nodes::Count.new([Arel.star]))
62
+
63
+ connection.select_values(sm, "#{self.class} Count").first
64
+ end
65
+
66
+ def create_table_and_set_flags(environment, schema_sha1 = nil)
67
+ return unless enabled?
68
+
69
+ create_table
70
+ update_or_create_entry(:environment, environment)
71
+ update_or_create_entry(:schema_sha1, schema_sha1) if schema_sha1
72
+ end
33
73
 
34
- def [](key)
35
- return unless enabled?
74
+ # Creates an internal metadata table with columns +key+ and +value+
75
+ def create_table
76
+ return unless enabled?
36
77
 
37
- where(key: key).pick(:value)
78
+ unless connection.table_exists?(table_name)
79
+ connection.create_table(table_name, id: false) do |t|
80
+ t.string :key, **connection.internal_string_options_for_primary_key
81
+ t.string :value
82
+ t.timestamps
83
+ end
38
84
  end
85
+ end
86
+
87
+ def drop_table
88
+ return unless enabled?
89
+
90
+ connection.drop_table table_name, if_exists: true
91
+ end
92
+
93
+ def table_exists?
94
+ connection.schema_cache.data_source_exists?(table_name)
95
+ end
39
96
 
40
- # Creates an internal metadata table with columns +key+ and +value+
41
- def create_table
42
- return unless enabled?
97
+ private
98
+ def update_or_create_entry(key, value)
99
+ entry = select_entry(key)
43
100
 
44
- unless connection.table_exists?(table_name)
45
- connection.create_table(table_name, id: false) do |t|
46
- t.string :key, **connection.internal_string_options_for_primary_key
47
- t.string :value
48
- t.timestamps
101
+ if entry
102
+ if entry[value_key] != value
103
+ update_entry(key, value)
104
+ else
105
+ entry[value_key]
49
106
  end
107
+ else
108
+ create_entry(key, value)
50
109
  end
51
110
  end
52
111
 
53
- def drop_table
54
- return unless enabled?
112
+ def current_time
113
+ connection.default_timezone == :utc ? Time.now.utc : Time.now
114
+ end
115
+
116
+ def create_entry(key, value)
117
+ im = Arel::InsertManager.new(arel_table)
118
+ im.insert [
119
+ [arel_table[primary_key], key],
120
+ [arel_table[value_key], value],
121
+ [arel_table[:created_at], current_time],
122
+ [arel_table[:updated_at], current_time]
123
+ ]
55
124
 
56
- connection.drop_table table_name, if_exists: true
125
+ connection.insert(im, "#{self.class} Create", primary_key, key)
126
+ end
127
+
128
+ def update_entry(key, new_value)
129
+ um = Arel::UpdateManager.new(arel_table)
130
+ um.set [
131
+ [arel_table[value_key], new_value],
132
+ [arel_table[:updated_at], current_time]
133
+ ]
134
+
135
+ um.where(arel_table[primary_key].eq(key))
136
+
137
+ connection.update(um, "#{self.class} Update")
138
+ end
139
+
140
+ def select_entry(key)
141
+ sm = Arel::SelectManager.new(arel_table)
142
+ sm.project(Arel::Nodes::SqlLiteral.new("*"))
143
+ sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
144
+ sm.order(arel_table[primary_key].asc)
145
+ sm.limit = 1
146
+
147
+ connection.select_all(sm, "#{self.class} Load").first
57
148
  end
58
- end
59
149
  end
60
150
  end
@@ -2,19 +2,19 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Locking
5
- # == What is Optimistic Locking
5
+ # == What is \Optimistic \Locking
6
6
  #
7
7
  # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
8
8
  # conflicts with the data. It does this by checking whether another process has made changes to a record since
9
- # it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
9
+ # it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
10
10
  # and the update is ignored.
11
11
  #
12
- # Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
12
+ # Check out +ActiveRecord::Locking::Pessimistic+ for an alternative.
13
13
  #
14
14
  # == Usage
15
15
  #
16
16
  # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
17
- # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
17
+ # record increments the integer column +lock_version+ and the locking facilities ensure that records instantiated twice
18
18
  # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
19
19
  #
20
20
  # p1 = Person.find(1)
@@ -69,6 +69,11 @@ module ActiveRecord
69
69
  end
70
70
  end
71
71
 
72
+ def initialize_dup(other) # :nodoc:
73
+ super
74
+ _clear_locking_column if locking_enabled?
75
+ end
76
+
72
77
  private
73
78
  def _create_record(attribute_names = self.attribute_names)
74
79
  if locking_enabled?
@@ -91,8 +96,7 @@ module ActiveRecord
91
96
  locking_column = self.class.locking_column
92
97
  lock_attribute_was = @attributes[locking_column]
93
98
 
94
- update_constraints = _primary_key_constraints_hash
95
- update_constraints[locking_column] = _lock_value_for_database(locking_column)
99
+ update_constraints = _query_constraints_hash
96
100
 
97
101
  attribute_names = attribute_names.dup if attribute_names.frozen?
98
102
  attribute_names << locking_column
@@ -118,16 +122,9 @@ module ActiveRecord
118
122
  end
119
123
 
120
124
  def destroy_row
121
- return super unless locking_enabled?
122
-
123
- locking_column = self.class.locking_column
125
+ affected_rows = super
124
126
 
125
- delete_constraints = _primary_key_constraints_hash
126
- delete_constraints[locking_column] = _lock_value_for_database(locking_column)
127
-
128
- affected_rows = self.class._delete_record(delete_constraints)
129
-
130
- if affected_rows != 1
127
+ if locking_enabled? && affected_rows != 1
131
128
  raise ActiveRecord::StaleObjectError.new(self, "destroy")
132
129
  end
133
130
 
@@ -142,6 +139,18 @@ module ActiveRecord
142
139
  end
143
140
  end
144
141
 
142
+ def _clear_locking_column
143
+ self[self.class.locking_column] = nil
144
+ clear_attribute_change(self.class.locking_column)
145
+ end
146
+
147
+ def _query_constraints_hash
148
+ return super unless locking_enabled?
149
+
150
+ locking_column = self.class.locking_column
151
+ super.merge(locking_column => _lock_value_for_database(locking_column))
152
+ end
153
+
145
154
  module ClassMethods
146
155
  DEFAULT_LOCKING_COLUMN = "lock_version"
147
156
 
@@ -159,10 +168,7 @@ module ActiveRecord
159
168
  end
160
169
 
161
170
  # The version column used for optimistic locking. Defaults to +lock_version+.
162
- def locking_column
163
- @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
164
- @locking_column
165
- end
171
+ attr_reader :locking_column
166
172
 
167
173
  # Reset the column used for optimistic locking back to the +lock_version+ default.
168
174
  def reset_locking_column
@@ -182,6 +188,14 @@ module ActiveRecord
182
188
  end
183
189
  super
184
190
  end
191
+
192
+ private
193
+ def inherited(base)
194
+ super
195
+ base.class_eval do
196
+ @locking_column = DEFAULT_LOCKING_COLUMN
197
+ end
198
+ end
185
199
  end
186
200
  end
187
201
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Locking
5
+ # = \Pessimistic \Locking
6
+ #
5
7
  # Locking::Pessimistic provides support for row-level locking using
6
8
  # SELECT ... FOR UPDATE and other lock types.
7
9
  #
@@ -71,6 +73,7 @@ module ActiveRecord
71
73
  Locking a record with unpersisted changes is not supported. Use
72
74
  `save` to persist the changes, or `reload` to discard them
73
75
  explicitly.
76
+ Changed attributes: #{changed.map(&:inspect).join(', ')}.
74
77
  MSG
75
78
  end
76
79
 
@@ -79,8 +82,8 @@ module ActiveRecord
79
82
  self
80
83
  end
81
84
 
82
- # Wraps the passed block in a transaction, locking the object
83
- # before yielding. You can pass the SQL locking clause
85
+ # Wraps the passed block in a transaction, reloading the object with a
86
+ # lock before yielding. You can pass the SQL locking clause
84
87
  # as an optional argument (see #lock!).
85
88
  #
86
89
  # You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,
@@ -7,16 +7,24 @@ module ActiveRecord
7
7
  class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
8
8
 
9
9
  def self.runtime=(value)
10
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
11
+ ActiveRecord::LogSubscriber.runtime= is deprecated and will be removed in Rails 7.2.
12
+ MSG
10
13
  ActiveRecord::RuntimeRegistry.sql_runtime = value
11
14
  end
12
15
 
13
16
  def self.runtime
14
- ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
17
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
18
+ ActiveRecord::LogSubscriber.runtime is deprecated and will be removed in Rails 7.2.
19
+ MSG
20
+ ActiveRecord::RuntimeRegistry.sql_runtime
15
21
  end
16
22
 
17
23
  def self.reset_runtime
18
- rt, self.runtime = runtime, 0
19
- rt
24
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
25
+ ActiveRecord::LogSubscriber.reset_runtime is deprecated and will be removed in Rails 7.2.
26
+ MSG
27
+ ActiveRecord::RuntimeRegistry.reset
20
28
  end
21
29
 
22
30
  def strict_loading_violation(event)
@@ -26,11 +34,9 @@ module ActiveRecord
26
34
  color(reflection.strict_loading_violation_message(owner), RED)
27
35
  end
28
36
  end
37
+ subscribe_log_level :strict_loading_violation, :debug
29
38
 
30
39
  def sql(event)
31
- self.class.runtime += event.duration
32
- return unless logger.debug?
33
-
34
40
  payload = event.payload
35
41
 
36
42
  return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
@@ -66,10 +72,11 @@ module ActiveRecord
66
72
  end
67
73
 
68
74
  name = colorize_payload_name(name, payload[:name])
69
- sql = color(sql, sql_color(sql), true) if colorize_logging
75
+ sql = color(sql, sql_color(sql), bold: true) if colorize_logging
70
76
 
71
77
  debug " #{name} #{sql}#{binds}"
72
78
  end
79
+ subscribe_log_level :sql, :debug
73
80
 
74
81
  private
75
82
  def type_casted_binds(casted_binds)
@@ -93,9 +100,9 @@ module ActiveRecord
93
100
 
94
101
  def colorize_payload_name(name, payload_name)
95
102
  if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
96
- color(name, MAGENTA, true)
103
+ color(name, MAGENTA, bold: true)
97
104
  else
98
- color(name, CYAN, true)
105
+ color(name, CYAN, bold: true)
99
106
  end
100
107
  end
101
108
 
@@ -133,15 +140,25 @@ module ActiveRecord
133
140
  end
134
141
 
135
142
  def log_query_source
136
- source = extract_query_source_location(caller)
143
+ source = query_source_location
137
144
 
138
145
  if source
139
146
  logger.debug(" ↳ #{source}")
140
147
  end
141
148
  end
142
149
 
143
- def extract_query_source_location(locations)
144
- backtrace_cleaner.clean(locations.lazy).first
150
+ if Thread.respond_to?(:each_caller_location)
151
+ def query_source_location
152
+ Thread.each_caller_location do |location|
153
+ frame = backtrace_cleaner.clean_frame(location)
154
+ return frame if frame
155
+ end
156
+ nil
157
+ end
158
+ else
159
+ def query_source_location
160
+ backtrace_cleaner.clean(caller(1).lazy).first
161
+ end
145
162
  end
146
163
 
147
164
  def filter(name, value)