activerecord 7.0.8 → 7.1.3.4

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 (231) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1554 -1452
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -16
  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 +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -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.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +313 -217
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +4 -4
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/pessimistic.rb +5 -2
  132. data/lib/active_record/log_subscriber.rb +29 -12
  133. data/lib/active_record/marshalling.rb +56 -0
  134. data/lib/active_record/message_pack.rb +124 -0
  135. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  136. data/lib/active_record/middleware/database_selector.rb +6 -8
  137. data/lib/active_record/middleware/shard_selector.rb +3 -1
  138. data/lib/active_record/migration/command_recorder.rb +104 -5
  139. data/lib/active_record/migration/compatibility.rb +139 -5
  140. data/lib/active_record/migration/default_strategy.rb +23 -0
  141. data/lib/active_record/migration/execution_strategy.rb +19 -0
  142. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  143. data/lib/active_record/migration.rb +219 -111
  144. data/lib/active_record/model_schema.rb +64 -44
  145. data/lib/active_record/nested_attributes.rb +24 -6
  146. data/lib/active_record/normalization.rb +167 -0
  147. data/lib/active_record/persistence.rb +188 -37
  148. data/lib/active_record/promise.rb +84 -0
  149. data/lib/active_record/query_cache.rb +3 -21
  150. data/lib/active_record/query_logs.rb +77 -52
  151. data/lib/active_record/query_logs_formatter.rb +41 -0
  152. data/lib/active_record/querying.rb +15 -2
  153. data/lib/active_record/railtie.rb +109 -47
  154. data/lib/active_record/railties/controller_runtime.rb +12 -6
  155. data/lib/active_record/railties/databases.rake +142 -148
  156. data/lib/active_record/railties/job_runtime.rb +23 -0
  157. data/lib/active_record/readonly_attributes.rb +32 -5
  158. data/lib/active_record/reflection.rb +174 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  160. data/lib/active_record/relation/batches.rb +190 -61
  161. data/lib/active_record/relation/calculations.rb +187 -63
  162. data/lib/active_record/relation/delegation.rb +23 -9
  163. data/lib/active_record/relation/finder_methods.rb +77 -16
  164. data/lib/active_record/relation/merger.rb +2 -0
  165. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  167. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  168. data/lib/active_record/relation/predicate_builder.rb +26 -14
  169. data/lib/active_record/relation/query_attribute.rb +2 -1
  170. data/lib/active_record/relation/query_methods.rb +352 -63
  171. data/lib/active_record/relation/spawn_methods.rb +18 -1
  172. data/lib/active_record/relation.rb +91 -35
  173. data/lib/active_record/result.rb +19 -5
  174. data/lib/active_record/runtime_registry.rb +24 -1
  175. data/lib/active_record/sanitization.rb +51 -11
  176. data/lib/active_record/schema.rb +2 -3
  177. data/lib/active_record/schema_dumper.rb +46 -7
  178. data/lib/active_record/schema_migration.rb +68 -33
  179. data/lib/active_record/scoping/default.rb +15 -5
  180. data/lib/active_record/scoping/named.rb +2 -2
  181. data/lib/active_record/scoping.rb +2 -1
  182. data/lib/active_record/secure_password.rb +60 -0
  183. data/lib/active_record/secure_token.rb +21 -3
  184. data/lib/active_record/signed_id.rb +7 -5
  185. data/lib/active_record/store.rb +8 -8
  186. data/lib/active_record/suppressor.rb +3 -1
  187. data/lib/active_record/table_metadata.rb +10 -1
  188. data/lib/active_record/tasks/database_tasks.rb +127 -105
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  192. data/lib/active_record/test_fixtures.rb +113 -96
  193. data/lib/active_record/timestamp.rb +27 -15
  194. data/lib/active_record/token_for.rb +113 -0
  195. data/lib/active_record/touch_later.rb +11 -6
  196. data/lib/active_record/transactions.rb +36 -10
  197. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  198. data/lib/active_record/type/internal/timezone.rb +7 -2
  199. data/lib/active_record/type/time.rb +4 -0
  200. data/lib/active_record/validations/absence.rb +1 -1
  201. data/lib/active_record/validations/numericality.rb +5 -4
  202. data/lib/active_record/validations/presence.rb +5 -28
  203. data/lib/active_record/validations/uniqueness.rb +47 -2
  204. data/lib/active_record/validations.rb +8 -4
  205. data/lib/active_record/version.rb +1 -1
  206. data/lib/active_record.rb +121 -16
  207. data/lib/arel/errors.rb +10 -0
  208. data/lib/arel/factory_methods.rb +4 -0
  209. data/lib/arel/nodes/binary.rb +6 -1
  210. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  211. data/lib/arel/nodes/cte.rb +36 -0
  212. data/lib/arel/nodes/fragments.rb +35 -0
  213. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  214. data/lib/arel/nodes/leading_join.rb +8 -0
  215. data/lib/arel/nodes/node.rb +111 -2
  216. data/lib/arel/nodes/sql_literal.rb +6 -0
  217. data/lib/arel/nodes/table_alias.rb +4 -0
  218. data/lib/arel/nodes.rb +4 -0
  219. data/lib/arel/predications.rb +2 -0
  220. data/lib/arel/table.rb +9 -5
  221. data/lib/arel/visitors/mysql.rb +8 -1
  222. data/lib/arel/visitors/to_sql.rb +81 -17
  223. data/lib/arel/visitors/visitor.rb +2 -2
  224. data/lib/arel.rb +16 -2
  225. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  226. data/lib/rails/generators/active_record/migration.rb +3 -1
  227. data/lib/rails/generators/active_record/model/USAGE +113 -0
  228. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  229. metadata +48 -12
  230. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  231. data/lib/active_record/null_relation.rb +0 -63
@@ -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,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)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Marshalling
5
+ @format_version = 6.1
6
+
7
+ class << self
8
+ attr_reader :format_version
9
+
10
+ def format_version=(version)
11
+ case version
12
+ when 6.1
13
+ Methods.remove_method(:marshal_dump) if Methods.method_defined?(:marshal_dump)
14
+ when 7.1
15
+ Methods.alias_method(:marshal_dump, :_marshal_dump_7_1)
16
+ else
17
+ raise ArgumentError, "Unknown marshalling format: #{version.inspect}"
18
+ end
19
+ @format_version = version
20
+ end
21
+ end
22
+
23
+ module Methods
24
+ def _marshal_dump_7_1
25
+ payload = [attributes_for_database, new_record?]
26
+
27
+ cached_associations = self.class.reflect_on_all_associations.select do |reflection|
28
+ association_cached?(reflection.name)
29
+ end
30
+
31
+ unless cached_associations.empty?
32
+ payload << cached_associations.map do |reflection|
33
+ [reflection.name, association(reflection.name).target]
34
+ end
35
+ end
36
+
37
+ payload
38
+ end
39
+
40
+ def marshal_load(state)
41
+ attributes_from_database, new_record, associations = state
42
+
43
+ attributes = self.class.attributes_builder.build_from_database(attributes_from_database)
44
+ init_with_attributes(attributes, new_record)
45
+
46
+ if associations
47
+ associations.each do |name, target|
48
+ association(name).target = target
49
+ rescue ActiveRecord::AssociationNotFoundError
50
+ # the association no longer exist, we can just skip it.
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module MessagePack # :nodoc:
5
+ FORMAT_VERSION = 1
6
+
7
+ class << self
8
+ def dump(input)
9
+ encoder = Encoder.new
10
+ [FORMAT_VERSION, encoder.encode(input), encoder.entries]
11
+ end
12
+
13
+ def load(dumped)
14
+ format_version, top_level, entries = dumped
15
+ unless format_version == FORMAT_VERSION
16
+ raise "Invalid format version: #{format_version.inspect}"
17
+ end
18
+ Decoder.new(entries).decode(top_level)
19
+ end
20
+ end
21
+
22
+ module Extensions
23
+ extend self
24
+
25
+ def install(registry)
26
+ registry.register_type 119, ActiveModel::Type::Binary::Data,
27
+ packer: :to_s,
28
+ unpacker: :new
29
+
30
+ registry.register_type 120, ActiveRecord::Base,
31
+ packer: method(:write_record),
32
+ unpacker: method(:read_record),
33
+ recursive: true
34
+ end
35
+
36
+ def write_record(record, packer)
37
+ packer.write(ActiveRecord::MessagePack.dump(record))
38
+ end
39
+
40
+ def read_record(unpacker)
41
+ ActiveRecord::MessagePack.load(unpacker.read)
42
+ end
43
+ end
44
+
45
+ class Encoder
46
+ attr_reader :entries
47
+
48
+ def initialize
49
+ @entries = []
50
+ @refs = {}.compare_by_identity
51
+ end
52
+
53
+ def encode(input)
54
+ if input.is_a?(Array)
55
+ input.map { |record| encode_record(record) }
56
+ elsif input
57
+ encode_record(input)
58
+ end
59
+ end
60
+
61
+ def encode_record(record)
62
+ ref = @refs[record]
63
+
64
+ if !ref
65
+ ref = @refs[record] = @entries.size
66
+ @entries << build_entry(record)
67
+ add_cached_associations(record, @entries.last)
68
+ end
69
+
70
+ ref
71
+ end
72
+
73
+ def build_entry(record)
74
+ [
75
+ ActiveSupport::MessagePack::Extensions.dump_class(record.class),
76
+ record.attributes_for_database,
77
+ record.new_record?
78
+ ]
79
+ end
80
+
81
+ def add_cached_associations(record, entry)
82
+ record.class.reflections.each_value do |reflection|
83
+ if record.association_cached?(reflection.name)
84
+ entry << reflection.name << encode(record.association(reflection.name).target)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ class Decoder
91
+ def initialize(entries)
92
+ @records = entries.map { |entry| build_record(entry) }
93
+ @records.zip(entries) { |record, entry| resolve_cached_associations(record, entry) }
94
+ end
95
+
96
+ def decode(ref)
97
+ if ref.is_a?(Array)
98
+ ref.map { |r| @records[r] }
99
+ elsif ref
100
+ @records[ref]
101
+ end
102
+ end
103
+
104
+ def build_record(entry)
105
+ class_name, attributes_hash, is_new_record, * = entry
106
+ klass = ActiveSupport::MessagePack::Extensions.load_class(class_name)
107
+ attributes = klass.attributes_builder.build_from_database(attributes_hash)
108
+ klass.allocate.init_with_attributes(attributes, is_new_record)
109
+ end
110
+
111
+ def resolve_cached_associations(record, entry)
112
+ i = 3 # entry == [class_name, attributes_hash, is_new_record, *associations]
113
+ while i < entry.length
114
+ begin
115
+ record.association(entry[i]).target = decode(entry[i + 1])
116
+ rescue ActiveRecord::AssociationNotFoundError
117
+ # The association no longer exists, so just skip it.
118
+ end
119
+ i += 2
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -48,6 +48,10 @@ module ActiveRecord
48
48
  context.save(response)
49
49
  end
50
50
 
51
+ def reading_request?(request)
52
+ request.get? || request.head?
53
+ end
54
+
51
55
  private
52
56
  def read_from_primary(&blk)
53
57
  ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: true) do