activerecord 7.2.2.1 → 8.1.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -28,6 +28,10 @@ module ActiveRecord
28
28
  # * +database+
29
29
  # * +source_location+
30
30
  #
31
+ # WARNING: Calculating the +source_location+ of a query can be slow, so you should consider its impact if using it in a production environment.
32
+ #
33
+ # Also see {config.active_record.verbose_query_logs}[https://guides.rubyonrails.org/debugging_rails_applications.html#verbose-query-logs].
34
+ #
31
35
  # Action Controller adds default tags when loaded:
32
36
  #
33
37
  # * +controller+
@@ -65,21 +69,77 @@ module ActiveRecord
65
69
  #
66
70
  # Tag comments can be prepended to the query:
67
71
  #
68
- # ActiveRecord::QueryLogs.prepend_comment = true
72
+ # config.active_record.query_log_tags_prepend_comment = true
69
73
  #
70
74
  # For applications where the content will not change during the lifetime of
71
75
  # the request or job execution, the tags can be cached for reuse in every query:
72
76
  #
73
77
  # config.active_record.cache_query_log_tags = true
74
78
  module QueryLogs
75
- mattr_accessor :taggings, instance_accessor: false, default: {}
76
- mattr_accessor :tags, instance_accessor: false, default: [ :application ]
77
- mattr_accessor :prepend_comment, instance_accessor: false, default: false
78
- mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
79
- mattr_accessor :tags_formatter, instance_accessor: false
79
+ class GetKeyHandler # :nodoc:
80
+ def initialize(name)
81
+ @name = name
82
+ end
83
+
84
+ def call(context)
85
+ context[@name]
86
+ end
87
+ end
88
+
89
+ class IdentityHandler # :nodoc:
90
+ def initialize(value)
91
+ @value = value
92
+ end
93
+
94
+ def call(_context)
95
+ @value
96
+ end
97
+ end
98
+
99
+ class ZeroArityHandler # :nodoc:
100
+ def initialize(proc)
101
+ @proc = proc
102
+ end
103
+
104
+ def call(_context)
105
+ @proc.call
106
+ end
107
+ end
108
+
109
+ @taggings = {}.freeze
110
+ @tags = [ :application ].freeze
111
+ @prepend_comment = false
112
+ @cache_query_log_tags = false
113
+ @tags_formatter = false
114
+
80
115
  thread_mattr_accessor :cached_comment, instance_accessor: false
81
116
 
82
117
  class << self
118
+ attr_reader :tags, :taggings, :tags_formatter # :nodoc:
119
+ attr_accessor :prepend_comment, :cache_query_log_tags # :nodoc:
120
+
121
+ def taggings=(taggings) # :nodoc:
122
+ @taggings = taggings.freeze
123
+ @handlers = rebuild_handlers
124
+ end
125
+
126
+ def tags=(tags) # :nodoc:
127
+ @tags = tags.freeze
128
+ @handlers = rebuild_handlers
129
+ end
130
+
131
+ def tags_formatter=(format) # :nodoc:
132
+ @formatter = case format
133
+ when :legacy
134
+ LegacyFormatter
135
+ when :sqlcommenter
136
+ SQLCommenter
137
+ else
138
+ raise ArgumentError, "Formatter is unsupported: #{format}"
139
+ end
140
+ @tags_formatter = format
141
+ end
142
+
83
143
  def call(sql, connection) # :nodoc:
84
144
  comment = self.comment(connection)
85
145
 
@@ -96,36 +156,42 @@ module ActiveRecord
96
156
  self.cached_comment = nil
97
157
  end
98
158
 
99
- # Updates the formatter to be what the passed in format is.
100
- def update_formatter(format)
101
- self.tags_formatter =
102
- case format
103
- when :legacy
104
- LegacyFormatter.new
105
- when :sqlcommenter
106
- SQLCommenter.new
107
- else
108
- raise ArgumentError, "Formatter is unsupported: #{formatter}"
109
- end
159
+ def query_source_location # :nodoc:
160
+ LogSubscriber.backtrace_cleaner.first_clean_frame
110
161
  end
111
162
 
112
- if Thread.respond_to?(:each_caller_location)
113
- def query_source_location # :nodoc:
114
- Thread.each_caller_location do |location|
115
- frame = LogSubscriber.backtrace_cleaner.clean_frame(location)
116
- return frame if frame
163
+ ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
164
+
165
+ private
166
+ def rebuild_handlers
167
+ handlers = []
168
+ @tags.each do |i|
169
+ if i.is_a?(Hash)
170
+ i.each do |k, v|
171
+ handlers << [k, build_handler(k, v)]
172
+ end
173
+ else
174
+ handlers << [i, build_handler(i)]
175
+ end
117
176
  end
118
- nil
119
- end
120
- else
121
- def query_source_location # :nodoc:
122
- LogSubscriber.backtrace_cleaner.clean(caller_locations(1).each).first
177
+ handlers.sort_by! { |(key, _)| key.to_s }
123
178
  end
124
- end
125
179
 
126
- ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
180
+ def build_handler(name, handler = nil)
181
+ handler ||= @taggings[name]
182
+ if handler.nil?
183
+ GetKeyHandler.new(name)
184
+ elsif handler.respond_to?(:call)
185
+ if handler.arity == 0
186
+ ZeroArityHandler.new(handler)
187
+ else
188
+ handler
189
+ end
190
+ else
191
+ IdentityHandler.new(handler)
192
+ end
193
+ end
127
194
 
128
- private
129
195
  # Returns an SQL comment +String+ containing the query log tags.
130
196
  # Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
131
197
  def comment(connection)
@@ -136,10 +202,6 @@ module ActiveRecord
136
202
  end
137
203
  end
138
204
 
139
- def formatter
140
- self.tags_formatter || self.update_formatter(:legacy)
141
- end
142
-
143
205
  def uncached_comment(connection)
144
206
  content = tag_content(connection)
145
207
 
@@ -149,7 +211,7 @@ module ActiveRecord
149
211
  end
150
212
 
151
213
  def escape_sql_comment(content)
152
- # Sanitize a string to appear within a SQL comment
214
+ # Sanitize a string to appear within an SQL comment
153
215
  # For compatibility, this also surrounding "/*+", "/*", and "*/"
154
216
  # characters, possibly with single surrounding space.
155
217
  # Then follows that by replacing any internal "*/" or "/ *" with
@@ -165,25 +227,15 @@ module ActiveRecord
165
227
  context = ActiveSupport::ExecutionContext.to_h
166
228
  context[:connection] ||= connection
167
229
 
168
- pairs = tags.flat_map { |i| [*i] }.filter_map do |tag|
169
- key, handler = tag
170
- handler ||= taggings[key]
171
-
172
- val = if handler.nil?
173
- context[key]
174
- elsif handler.respond_to?(:call)
175
- if handler.arity == 0
176
- handler.call
177
- else
178
- handler.call(context)
179
- end
180
- else
181
- handler
182
- end
183
- [key, val] unless val.nil?
230
+ pairs = @handlers.filter_map do |(key, handler)|
231
+ val = handler.call(context)
232
+ @formatter.format(key, val) unless val.nil?
184
233
  end
185
- self.formatter.format(pairs)
234
+ @formatter.join(pairs)
186
235
  end
187
236
  end
237
+
238
+ @handlers = rebuild_handlers
239
+ self.tags_formatter = :legacy
188
240
  end
189
241
  end
@@ -2,40 +2,29 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module QueryLogs
5
- class LegacyFormatter # :nodoc:
6
- def initialize
7
- @key_value_separator = ":"
8
- end
9
-
10
- # Formats the key value pairs into a string.
11
- def format(pairs)
12
- pairs.map! do |key, value|
13
- "#{key}#{key_value_separator}#{format_value(value)}"
14
- end.join(",")
15
- end
16
-
17
- private
18
- attr_reader :key_value_separator
19
-
20
- def format_value(value)
21
- value
5
+ module LegacyFormatter # :nodoc:
6
+ class << self
7
+ # Formats the key value pairs into a string.
8
+ def format(key, value)
9
+ "#{key}:#{value}"
22
10
  end
23
- end
24
11
 
25
- class SQLCommenter < LegacyFormatter # :nodoc:
26
- def initialize
27
- @key_value_separator = "="
12
+ def join(pairs)
13
+ pairs.join(",")
14
+ end
28
15
  end
16
+ end
29
17
 
30
- def format(pairs)
31
- pairs.sort_by! { |pair| pair.first.to_s }
32
- super
33
- end
18
+ class SQLCommenter # :nodoc:
19
+ class << self
20
+ def format(key, value)
21
+ "#{key}='#{ERB::Util.url_encode(value)}'"
22
+ end
34
23
 
35
- private
36
- def format_value(value)
37
- "'#{ERB::Util.url_encode(value)}'"
24
+ def join(pairs)
25
+ pairs.join(",")
38
26
  end
27
+ end
39
28
  end
40
29
  end
41
30
  end
@@ -46,8 +46,8 @@ module ActiveRecord
46
46
  # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
47
47
  # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
48
48
  #
49
- # Note that building your own SQL query string from user input may expose your application to
50
- # injection attacks (https://guides.rubyonrails.org/security.html#sql-injection).
49
+ # Note that building your own SQL query string from user input {may expose your application to
50
+ # injection attacks}[https://guides.rubyonrails.org/security.html#sql-injection].
51
51
  def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
52
52
  result = with_connection do |c|
53
53
  _query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry)
@@ -55,13 +55,11 @@ module ActiveRecord
55
55
  _load_from_sql(result, &block)
56
56
  end
57
57
 
58
- # Same as <tt>#find_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
59
- def async_find_by_sql(sql, binds = [], preparable: nil, &block)
60
- result = with_connection do |c|
61
- _query_by_sql(c, sql, binds, preparable: preparable, async: true)
62
- end
63
-
64
- result.then do |result|
58
+ # Same as #find_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
59
+ def async_find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
60
+ with_connection do |c|
61
+ _query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry, async: true)
62
+ end.then do |result|
65
63
  _load_from_sql(result, &block)
66
64
  end
67
65
  end
@@ -71,6 +69,8 @@ module ActiveRecord
71
69
  end
72
70
 
73
71
  def _load_from_sql(result_set, &block) # :nodoc:
72
+ return [] if result_set.empty?
73
+
74
74
  column_types = result_set.column_types
75
75
 
76
76
  unless column_types.empty?
@@ -86,10 +86,10 @@ module ActiveRecord
86
86
 
87
87
  message_bus.instrument("instantiation.active_record", payload) do
88
88
  if result_set.includes_column?(inheritance_column)
89
- result_set.map { |record| instantiate(record, column_types, &block) }
89
+ result_set.indexed_rows.map { |record| instantiate(record, column_types, &block) }
90
90
  else
91
91
  # Instantiate a homogeneous set
92
- result_set.map { |record| instantiate_instance_of(self, record, column_types, &block) }
92
+ result_set.indexed_rows.map { |record| instantiate_instance_of(self, record, column_types, &block) }
93
93
  end
94
94
  end
95
95
  end
@@ -112,7 +112,7 @@ module ActiveRecord
112
112
  end
113
113
  end
114
114
 
115
- # Same as <tt>#count_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
115
+ # Same as #count_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
116
116
  def async_count_by_sql(sql)
117
117
  with_connection do |c|
118
118
  c.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
3
  require "rails"
4
+ require "active_record"
5
5
  require "active_support/core_ext/object/try"
6
6
  require "active_model/railtie"
7
7
 
@@ -23,8 +23,8 @@ module ActiveRecord
23
23
  config.action_dispatch.rescue_responses.merge!(
24
24
  "ActiveRecord::RecordNotFound" => :not_found,
25
25
  "ActiveRecord::StaleObjectError" => :conflict,
26
- "ActiveRecord::RecordInvalid" => :unprocessable_entity,
27
- "ActiveRecord::RecordNotSaved" => :unprocessable_entity
26
+ "ActiveRecord::RecordInvalid" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT,
27
+ "ActiveRecord::RecordNotSaved" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT
28
28
  )
29
29
 
30
30
  config.active_record.use_schema_cache_dump = true
@@ -35,9 +35,12 @@ module ActiveRecord
35
35
  config.active_record.query_log_tags = [ :application ]
36
36
  config.active_record.query_log_tags_format = :legacy
37
37
  config.active_record.cache_query_log_tags = false
38
+ config.active_record.query_log_tags_prepend_comment = false
38
39
  config.active_record.raise_on_assign_to_attr_readonly = false
39
40
  config.active_record.belongs_to_required_validates_foreign_key = true
40
41
  config.active_record.generate_secure_token_on = :create
42
+ config.active_record.use_legacy_signed_id_verifier = :generate_and_verify
43
+ config.active_record.deprecated_associations_options = { mode: :warn, backtrace: false }
41
44
 
42
45
  config.active_record.queues = ActiveSupport::InheritableOptions.new
43
46
 
@@ -184,30 +187,6 @@ To keep using the current cache store, you can turn off cache versioning entirel
184
187
  end
185
188
  end
186
189
 
187
- initializer "active_record.warn_on_records_fetched_greater_than" do
188
- if config.active_record.warn_on_records_fetched_greater_than
189
- ActiveRecord.deprecator.warn <<~MSG.squish
190
- `config.active_record.warn_on_records_fetched_greater_than` is deprecated and will be
191
- removed in Rails 8.0.
192
- Please subscribe to `sql.active_record` notifications and access the row count field to
193
- detect large result set sizes.
194
- MSG
195
- ActiveSupport.on_load(:active_record) do
196
- require "active_record/relation/record_fetch_warning"
197
- end
198
- end
199
- end
200
-
201
- initializer "active_record.sqlite3_deprecated_warning" do
202
- if config.active_record.key?(:sqlite3_production_warning)
203
- config.active_record.delete(:sqlite3_production_warning)
204
- ActiveRecord.deprecator.warn <<~MSG.squish
205
- The `config.active_record.sqlite3_production_warning` configuration no longer has any effect
206
- and can be safely removed.
207
- MSG
208
- end
209
- end
210
-
211
190
  initializer "active_record.sqlite3_adapter_strict_strings_by_default" do
212
191
  config.after_initialize do
213
192
  if config.active_record.sqlite3_adapter_strict_strings_by_default
@@ -253,10 +232,12 @@ To keep using the current cache store, you can turn off cache versioning entirel
253
232
  :query_log_tags,
254
233
  :query_log_tags_format,
255
234
  :cache_query_log_tags,
235
+ :query_log_tags_prepend_comment,
256
236
  :sqlite3_adapter_strict_strings_by_default,
257
237
  :check_schema_cache_dump_version,
258
238
  :use_schema_cache_dump,
259
239
  :postgresql_adapter_decode_dates,
240
+ :use_legacy_signed_id_verifier,
260
241
  )
261
242
 
262
243
  configs_used_in_other_initializers.each do |k, v|
@@ -298,6 +279,13 @@ To keep using the current cache store, you can turn off cache versioning entirel
298
279
  end
299
280
  end
300
281
 
282
+ initializer "active_record.job_checkpoints" do
283
+ require "active_record/railties/job_checkpoints"
284
+ ActiveSupport.on_load(:active_job_continuable) do
285
+ prepend ActiveRecord::Railties::JobCheckpoints
286
+ end
287
+ end
288
+
301
289
  initializer "active_record.set_reloader_hooks" do
302
290
  ActiveSupport.on_load(:active_record) do
303
291
  ActiveSupport::Reloader.before_class_unload do
@@ -343,9 +331,22 @@ To keep using the current cache store, you can turn off cache versioning entirel
343
331
  end
344
332
  end
345
333
 
346
- initializer "active_record.set_signed_id_verifier_secret" do
347
- ActiveSupport.on_load(:active_record) do
348
- self.signed_id_verifier_secret ||= -> { Rails.application.key_generator.generate_key("active_record/signed_id") }
334
+ initializer "active_record.filter_attributes_as_log_parameters" do |app|
335
+ ActiveRecord::FilterAttributeHandler.new(app).enable
336
+ end
337
+
338
+ initializer "active_record.configure_message_verifiers" do |app|
339
+ ActiveRecord.message_verifiers = app.message_verifiers
340
+
341
+ use_legacy_signed_id_verifier = app.config.active_record.use_legacy_signed_id_verifier
342
+ legacy_options = { digest: "SHA256", serializer: JSON, url_safe: true }
343
+
344
+ if use_legacy_signed_id_verifier == :generate_and_verify
345
+ app.message_verifiers.prepend { |salt| legacy_options if salt == "active_record/signed_id" }
346
+ elsif use_legacy_signed_id_verifier == :verify
347
+ app.message_verifiers.rotate { |salt| legacy_options if salt == "active_record/signed_id" }
348
+ elsif use_legacy_signed_id_verifier
349
+ raise ArgumentError, "Unrecognized value for config.active_record.use_legacy_signed_id_verifier: #{use_legacy_signed_id_verifier.inspect}"
349
350
  end
350
351
  end
351
352
 
@@ -390,7 +391,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
390
391
  config.after_initialize do
391
392
  if app.config.active_record.query_log_tags_enabled
392
393
  ActiveRecord.query_transformers << ActiveRecord::QueryLogs
393
- ActiveRecord::QueryLogs.taggings.merge!(
394
+ ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
394
395
  application: Rails.application.class.name.split("::").first,
395
396
  pid: -> { Process.pid.to_s },
396
397
  socket: ->(context) { context[:connection].pool.db_config.socket },
@@ -405,12 +406,16 @@ To keep using the current cache store, you can turn off cache versioning entirel
405
406
  end
406
407
 
407
408
  if app.config.active_record.query_log_tags_format
408
- ActiveRecord::QueryLogs.update_formatter(app.config.active_record.query_log_tags_format)
409
+ ActiveRecord::QueryLogs.tags_formatter = app.config.active_record.query_log_tags_format
409
410
  end
410
411
 
411
412
  if app.config.active_record.cache_query_log_tags
412
413
  ActiveRecord::QueryLogs.cache_query_log_tags = true
413
414
  end
415
+
416
+ if app.config.active_record.query_log_tags_prepend_comment
417
+ ActiveRecord::QueryLogs.prepend_comment = true
418
+ end
414
419
  end
415
420
  end
416
421
  end
@@ -41,11 +41,14 @@ module ActiveRecord
41
41
 
42
42
  def cleanup_view_runtime
43
43
  if logger && logger.info?
44
- db_rt_before_render = ActiveRecord::RuntimeRegistry.reset_runtimes
44
+ runtime_stats = ActiveRecord::RuntimeRegistry.stats
45
+ db_rt_before_render = runtime_stats.reset_runtimes
45
46
  self.db_runtime = (db_runtime || 0) + db_rt_before_render
47
+
46
48
  runtime = super
47
- queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime
48
- db_rt_after_render = ActiveRecord::RuntimeRegistry.reset_runtimes
49
+
50
+ queries_rt = runtime_stats.sql_runtime - runtime_stats.async_sql_runtime
51
+ db_rt_after_render = runtime_stats.reset_runtimes
49
52
  self.db_runtime += db_rt_after_render
50
53
  runtime - queries_rt
51
54
  else
@@ -56,9 +59,11 @@ module ActiveRecord
56
59
  def append_info_to_payload(payload)
57
60
  super
58
61
 
59
- payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::RuntimeRegistry.reset_runtimes
60
- payload[:queries_count] = ActiveRecord::RuntimeRegistry.reset_queries_count
61
- payload[:cached_queries_count] = ActiveRecord::RuntimeRegistry.reset_cached_queries_count
62
+ runtime_stats = ActiveRecord::RuntimeRegistry.stats
63
+ payload[:db_runtime] = (db_runtime || 0) + runtime_stats.sql_runtime
64
+ payload[:queries_count] = runtime_stats.queries_count
65
+ payload[:cached_queries_count] = runtime_stats.cached_queries_count
66
+ runtime_stats.reset
62
67
  end
63
68
  end
64
69
  end
@@ -87,22 +87,7 @@ db_namespace = namespace :db do
87
87
 
88
88
  desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
89
89
  task migrate: :load_config do
90
- db_configs = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env)
91
-
92
- if db_configs.size == 1 && db_configs.first.primary?
93
- ActiveRecord::Tasks::DatabaseTasks.migrate
94
- else
95
- mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions
96
-
97
- mapped_versions.sort.each do |version, db_configs|
98
- db_configs.each do |db_config|
99
- ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection(db_config) do
100
- ActiveRecord::Tasks::DatabaseTasks.migrate(version)
101
- end
102
- end
103
- end
104
- end
105
-
90
+ ActiveRecord::Tasks::DatabaseTasks.migrate_all
106
91
  db_namespace["_dump"].invoke
107
92
  end
108
93
 
@@ -175,8 +160,20 @@ db_namespace = namespace :db do
175
160
  end
176
161
  end
177
162
 
178
- # desc 'Resets your database using your migrations for the current environment'
179
- task reset: ["db:drop", "db:create", "db:migrate"]
163
+ desc "Resets your database using your migrations for the current environment"
164
+ task reset: ["db:drop", "db:create", "db:schema:dump", "db:migrate"]
165
+
166
+ namespace :reset do
167
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
168
+ desc "Drop and recreate the #{name} database using migrations"
169
+ task name => :load_config do
170
+ db_namespace["drop:#{name}"].invoke
171
+ db_namespace["create:#{name}"].invoke
172
+ db_namespace["schema:dump:#{name}"].invoke
173
+ db_namespace["migrate:#{name}"].invoke
174
+ end
175
+ end
176
+ end
180
177
 
181
178
  desc 'Run the "up" for a given migration VERSION.'
182
179
  task up: :load_config do
@@ -348,7 +345,7 @@ db_namespace = namespace :db do
348
345
  pending_migrations << pool.migration_context.open.pending_migrations
349
346
  end
350
347
 
351
- pending_migrations = pending_migrations.flatten!
348
+ pending_migrations.flatten!
352
349
 
353
350
  if pending_migrations.any?
354
351
  puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
@@ -462,28 +459,23 @@ db_namespace = namespace :db do
462
459
  namespace :schema do
463
460
  desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)"
464
461
  task dump: :load_config do
465
- ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each do |pool|
466
- db_config = pool.db_config
467
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
468
- ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
469
- end
462
+ ActiveRecord::Tasks::DatabaseTasks.dump_all
470
463
 
471
464
  db_namespace["schema:dump"].reenable
472
465
  end
473
466
 
474
467
  desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database"
475
468
  task load: [:load_config, :check_protected_environments] do
476
- ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord.schema_format, ENV["SCHEMA"])
469
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ENV["SCHEMA_FORMAT"], ENV["SCHEMA"])
477
470
  end
478
471
 
479
472
  namespace :dump do
480
473
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
481
- desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) for #{name} database"
474
+ desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) for #{name} database"
482
475
  task name => :load_config do
483
476
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
484
477
  db_config = pool.db_config
485
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
486
- ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
478
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
487
479
  end
488
480
 
489
481
  db_namespace["schema:dump:#{name}"].reenable
@@ -493,12 +485,11 @@ db_namespace = namespace :db do
493
485
 
494
486
  namespace :load do
495
487
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
496
- desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the #{name} database"
497
- task name => "db:test:purge:#{name}" do
488
+ desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) into the #{name} database"
489
+ task name => [:load_config, :check_protected_environments] do
498
490
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
499
491
  db_config = pool.db_config
500
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
501
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
492
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
502
493
  end
503
494
  end
504
495
  end
@@ -542,13 +533,12 @@ db_namespace = namespace :db do
542
533
  end
543
534
 
544
535
  namespace :test do
545
- # desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)"
536
+ # desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on configuration)"
546
537
  task load_schema: %w(db:test:purge) do
547
538
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test") do |pool|
548
539
  db_config = pool.db_config
549
540
  ActiveRecord::Schema.verbose = false
550
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
551
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
541
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
552
542
  end
553
543
  end
554
544
 
@@ -573,8 +563,7 @@ db_namespace = namespace :db do
573
563
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test", name: name) do |pool|
574
564
  db_config = pool.db_config
575
565
  ActiveRecord::Schema.verbose = false
576
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
577
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
566
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
578
567
  end
579
568
  end
580
569
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Railties # :nodoc:
5
+ module JobCheckpoints # :nodoc:
6
+ def checkpoint!
7
+ if ActiveRecord.all_open_transactions.any?
8
+ raise ActiveJob::Continuation::CheckpointError, "Cannot checkpoint job with open transactions"
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end