activerecord 7.1.5.2 → 8.1.2.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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -3,42 +3,60 @@
3
3
  module ActiveRecord
4
4
  # = Active Record Query Cache
5
5
  class QueryCache
6
+ # ActiveRecord::Base extends this module, so these methods are available in models.
6
7
  module ClassMethods
7
8
  # Enable the query cache within the block if Active Record is configured.
8
9
  # If it's not, it will execute the given block.
9
10
  def cache(&block)
10
11
  if connected? || !configurations.empty?
11
- connection.cache(&block)
12
+ pool = connection_pool
13
+ was_enabled = pool.query_cache_enabled
14
+ begin
15
+ pool.enable_query_cache(&block)
16
+ ensure
17
+ pool.clear_query_cache unless was_enabled
18
+ end
12
19
  else
13
20
  yield
14
21
  end
15
22
  end
16
23
 
17
- # Disable the query cache within the block if Active Record is configured.
18
- # If it's not, it will execute the given block.
19
- def uncached(&block)
24
+ # Runs the block with the query cache disabled.
25
+ #
26
+ # If the query cache was enabled before the block was executed, it is
27
+ # enabled again after it.
28
+ #
29
+ # Set <tt>dirties: false</tt> to prevent query caches on all connections
30
+ # from being cleared by write operations. (By default, write operations
31
+ # dirty all connections' query caches in case they are replicas whose
32
+ # cache would now be outdated.)
33
+ def uncached(dirties: true, &block)
20
34
  if connected? || !configurations.empty?
21
- connection.uncached(&block)
35
+ connection_pool.disable_query_cache(dirties: dirties, &block)
22
36
  else
23
37
  yield
24
38
  end
25
39
  end
26
40
  end
27
41
 
28
- def self.run
29
- ActiveRecord::Base.connection_handler.each_connection_pool.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! }
30
- end
31
-
32
- def self.complete(pools)
33
- pools.each { |pool| pool.disable_query_cache! unless pool.discarded? }
42
+ module ExecutorHooks # :nodoc:
43
+ def self.run
44
+ ActiveRecord::Base.connection_handler.each_connection_pool.reject(&:query_cache_enabled).each do |pool|
45
+ next if pool.db_config&.query_cache == false
46
+ pool.enable_query_cache!
47
+ end
48
+ end
34
49
 
35
- ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
36
- pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
50
+ def self.complete(pools)
51
+ pools.each do |pool|
52
+ pool.disable_query_cache!
53
+ pool.clear_query_cache
54
+ end
37
55
  end
38
56
  end
39
57
 
40
- def self.install_executor_hooks(executor = ActiveSupport::Executor)
41
- executor.register_hook(self)
58
+ def self.install_executor_hooks(executor = ActiveSupport::Executor) # :nodoc:
59
+ executor.register_hook(ExecutorHooks)
42
60
  end
43
61
  end
44
62
  end
@@ -26,6 +26,11 @@ module ActiveRecord
26
26
  # * +socket+
27
27
  # * +db_host+
28
28
  # * +database+
29
+ # * +source_location+
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].
29
34
  #
30
35
  # Action Controller adds default tags when loaded:
31
36
  #
@@ -64,21 +69,77 @@ module ActiveRecord
64
69
  #
65
70
  # Tag comments can be prepended to the query:
66
71
  #
67
- # ActiveRecord::QueryLogs.prepend_comment = true
72
+ # config.active_record.query_log_tags_prepend_comment = true
68
73
  #
69
74
  # For applications where the content will not change during the lifetime of
70
75
  # the request or job execution, the tags can be cached for reuse in every query:
71
76
  #
72
77
  # config.active_record.cache_query_log_tags = true
73
78
  module QueryLogs
74
- mattr_accessor :taggings, instance_accessor: false, default: {}
75
- mattr_accessor :tags, instance_accessor: false, default: [ :application ]
76
- mattr_accessor :prepend_comment, instance_accessor: false, default: false
77
- mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
78
- 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
+
79
115
  thread_mattr_accessor :cached_comment, instance_accessor: false
80
116
 
81
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
+
82
143
  def call(sql, connection) # :nodoc:
83
144
  comment = self.comment(connection)
84
145
 
@@ -95,22 +156,42 @@ module ActiveRecord
95
156
  self.cached_comment = nil
96
157
  end
97
158
 
98
- # Updates the formatter to be what the passed in format is.
99
- def update_formatter(format)
100
- self.tags_formatter =
101
- case format
102
- when :legacy
103
- LegacyFormatter.new
104
- when :sqlcommenter
105
- SQLCommenter.new
106
- else
107
- raise ArgumentError, "Formatter is unsupported: #{formatter}"
108
- end
159
+ def query_source_location # :nodoc:
160
+ LogSubscriber.backtrace_cleaner.first_clean_frame
109
161
  end
110
162
 
111
163
  ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
112
164
 
113
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
176
+ end
177
+ handlers.sort_by! { |(key, _)| key.to_s }
178
+ end
179
+
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
194
+
114
195
  # Returns an SQL comment +String+ containing the query log tags.
115
196
  # Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
116
197
  def comment(connection)
@@ -121,10 +202,6 @@ module ActiveRecord
121
202
  end
122
203
  end
123
204
 
124
- def formatter
125
- self.tags_formatter || self.update_formatter(:legacy)
126
- end
127
-
128
205
  def uncached_comment(connection)
129
206
  content = tag_content(connection)
130
207
 
@@ -134,7 +211,7 @@ module ActiveRecord
134
211
  end
135
212
 
136
213
  def escape_sql_comment(content)
137
- # Sanitize a string to appear within a SQL comment
214
+ # Sanitize a string to appear within an SQL comment
138
215
  # For compatibility, this also surrounding "/*+", "/*", and "*/"
139
216
  # characters, possibly with single surrounding space.
140
217
  # Then follows that by replacing any internal "*/" or "/ *" with
@@ -150,25 +227,15 @@ module ActiveRecord
150
227
  context = ActiveSupport::ExecutionContext.to_h
151
228
  context[:connection] ||= connection
152
229
 
153
- pairs = tags.flat_map { |i| [*i] }.filter_map do |tag|
154
- key, handler = tag
155
- handler ||= taggings[key]
156
-
157
- val = if handler.nil?
158
- context[key]
159
- elsif handler.respond_to?(:call)
160
- if handler.arity == 0
161
- handler.call
162
- else
163
- handler.call(context)
164
- end
165
- else
166
- handler
167
- end
168
- [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?
169
233
  end
170
- self.formatter.format(pairs)
234
+ @formatter.join(pairs)
171
235
  end
172
236
  end
237
+
238
+ @handlers = rebuild_handlers
239
+ self.tags_formatter = :legacy
173
240
  end
174
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
@@ -10,15 +10,16 @@ module ActiveRecord
10
10
  :first_or_create, :first_or_create!, :first_or_initialize,
11
11
  :find_or_create_by, :find_or_create_by!, :find_or_initialize_by,
12
12
  :create_or_find_by, :create_or_find_by!,
13
- :destroy_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by,
13
+ :destroy, :destroy_all, :delete, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by,
14
14
  :find_each, :find_in_batches, :in_batches,
15
15
  :select, :reselect, :order, :regroup, :in_order_of, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins,
16
16
  :where, :rewhere, :invert_where, :preload, :extract_associated, :eager_load, :includes, :from, :lock, :readonly,
17
17
  :and, :or, :annotate, :optimizer_hints, :extending,
18
18
  :having, :create_with, :distinct, :references, :none, :unscope, :merge, :except, :only,
19
19
  :count, :average, :minimum, :maximum, :sum, :calculate,
20
- :pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with,
20
+ :pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with, :with_recursive,
21
21
  :async_count, :async_average, :async_minimum, :async_maximum, :async_sum, :async_pluck, :async_pick,
22
+ :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
22
23
  ].freeze # :nodoc:
23
24
  delegate(*QUERYING_METHODS, to: :all)
24
25
 
@@ -45,24 +46,31 @@ module ActiveRecord
45
46
  # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
46
47
  # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
47
48
  #
48
- # Note that building your own SQL query string from user input may expose your application to
49
- # injection attacks (https://guides.rubyonrails.org/security.html#sql-injection).
50
- def find_by_sql(sql, binds = [], preparable: nil, &block)
51
- _load_from_sql(_query_by_sql(sql, binds, preparable: preparable), &block)
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
+ def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
52
+ result = with_connection do |c|
53
+ _query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry)
54
+ end
55
+ _load_from_sql(result, &block)
52
56
  end
53
57
 
54
- # Same as <tt>#find_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
55
- def async_find_by_sql(sql, binds = [], preparable: nil, &block)
56
- _query_by_sql(sql, binds, preparable: preparable, async: true).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|
57
63
  _load_from_sql(result, &block)
58
64
  end
59
65
  end
60
66
 
61
- def _query_by_sql(sql, binds = [], preparable: nil, async: false) # :nodoc:
62
- connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable, async: async)
67
+ def _query_by_sql(connection, sql, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
68
+ connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable, async: async, allow_retry: allow_retry)
63
69
  end
64
70
 
65
71
  def _load_from_sql(result_set, &block) # :nodoc:
72
+ return [] if result_set.empty?
73
+
66
74
  column_types = result_set.column_types
67
75
 
68
76
  unless column_types.empty?
@@ -78,10 +86,10 @@ module ActiveRecord
78
86
 
79
87
  message_bus.instrument("instantiation.active_record", payload) do
80
88
  if result_set.includes_column?(inheritance_column)
81
- result_set.map { |record| instantiate(record, column_types, &block) }
89
+ result_set.indexed_rows.map { |record| instantiate(record, column_types, &block) }
82
90
  else
83
91
  # Instantiate a homogeneous set
84
- 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) }
85
93
  end
86
94
  end
87
95
  end
@@ -99,12 +107,16 @@ module ActiveRecord
99
107
  #
100
108
  # * +sql+ - An SQL statement which should return a count query from the database, see the example above.
101
109
  def count_by_sql(sql)
102
- connection.select_value(sanitize_sql(sql), "#{name} Count").to_i
110
+ with_connection do |c|
111
+ c.select_value(sanitize_sql(sql), "#{name} Count").to_i
112
+ end
103
113
  end
104
114
 
105
- # 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.
106
116
  def async_count_by_sql(sql)
107
- connection.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
117
+ with_connection do |c|
118
+ c.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
119
+ end
108
120
  end
109
121
  end
110
122
  end
@@ -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,22 +23,24 @@ 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
31
31
  config.active_record.check_schema_cache_dump_version = true
32
32
  config.active_record.maintain_test_schema = true
33
33
  config.active_record.has_many_inversing = false
34
- config.active_record.sqlite3_production_warning = true
35
34
  config.active_record.query_log_tags_enabled = false
36
35
  config.active_record.query_log_tags = [ :application ]
37
36
  config.active_record.query_log_tags_format = :legacy
38
37
  config.active_record.cache_query_log_tags = false
38
+ config.active_record.query_log_tags_prepend_comment = false
39
39
  config.active_record.raise_on_assign_to_attr_readonly = false
40
40
  config.active_record.belongs_to_required_validates_foreign_key = true
41
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 }
42
44
 
43
45
  config.active_record.queues = ActiveSupport::InheritableOptions.new
44
46
 
@@ -70,6 +72,7 @@ module ActiveRecord
70
72
  Rails.logger.broadcast_to(console)
71
73
  end
72
74
  ActiveRecord.verbose_query_logs = false
75
+ ActiveRecord::Base.attributes_for_inspect = :all
73
76
  end
74
77
 
75
78
  runner do
@@ -88,7 +91,9 @@ module ActiveRecord
88
91
 
89
92
  initializer "active_record.postgresql_time_zone_aware_types" do
90
93
  ActiveSupport.on_load(:active_record_postgresqladapter) do
91
- ActiveRecord::Base.time_zone_aware_types << :timestamptz
94
+ ActiveSupport.on_load(:active_record) do
95
+ ActiveRecord::Base.time_zone_aware_types << :timestamptz
96
+ end
92
97
  end
93
98
  end
94
99
 
@@ -133,49 +138,11 @@ To keep using the current cache store, you can turn off cache versioning entirel
133
138
  end
134
139
  end
135
140
 
136
- initializer "active_record.use_schema_cache_dump" do
137
- ActiveRecord::ConnectionAdapters::SchemaReflection.use_schema_cache_dump = config.active_record.use_schema_cache_dump
138
- end
139
-
140
- initializer "active_record.check_schema_cache_dump" do
141
- check_schema_cache_dump_version = config.active_record.check_schema_cache_dump_version
142
-
143
- ActiveRecord::ConnectionAdapters::SchemaReflection.check_schema_cache_dump_version = check_schema_cache_dump_version
144
-
145
- if config.active_record.use_schema_cache_dump && !config.active_record.lazily_load_schema_cache
146
- config.after_initialize do |app|
147
- ActiveSupport.on_load(:active_record) do
148
- db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
149
- next if db_config.nil?
150
-
151
- filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
152
- db_config.name,
153
- schema_cache_path: db_config.schema_cache_path
154
- )
155
-
156
- cache = ActiveRecord::ConnectionAdapters::SchemaCache._load_from(filename)
157
- next if cache.nil?
141
+ initializer "active_record.copy_schema_cache_config" do
142
+ active_record_config = config.active_record
158
143
 
159
- if check_schema_cache_dump_version
160
- current_version = begin
161
- ActiveRecord::Migrator.current_version
162
- rescue ActiveRecordError => error
163
- warn "Failed to validate the schema cache because of #{error.class}: #{error.message}"
164
- nil
165
- end
166
- next if current_version.nil?
167
-
168
- if cache.schema_version != current_version
169
- warn "Ignoring #{filename} because it has expired. The current schema version is #{current_version}, but the one in the schema cache file is #{cache.schema_version}."
170
- next
171
- end
172
- end
173
-
174
- Rails.logger.info("Using schema cache file #{filename}")
175
- connection_pool.schema_reflection.set_schema_cache(cache)
176
- end
177
- end
178
- end
144
+ ActiveRecord::ConnectionAdapters::SchemaReflection.use_schema_cache_dump = active_record_config.use_schema_cache_dump
145
+ ActiveRecord::ConnectionAdapters::SchemaReflection.check_schema_cache_dump_version = active_record_config.check_schema_cache_dump_version
179
146
  end
180
147
 
181
148
  initializer "active_record.define_attribute_methods" do |app|
@@ -198,7 +165,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
198
165
  # Additionally if `check_schema_cache_dump_version` is enabled (which is the default),
199
166
  # loading the schema cache dump trigger a database connection to compare the schema
200
167
  # versions.
201
- # This means the attribute methods will be lazily defined whent the model is accessed,
168
+ # This means the attribute methods will be lazily defined when the model is accessed,
202
169
  # likely as part of the first few requests or jobs. This isn't good for performance
203
170
  # but we unfortunately have to arbitrate between resiliency and performance, and chose
204
171
  # resiliency.
@@ -220,24 +187,6 @@ To keep using the current cache store, you can turn off cache versioning entirel
220
187
  end
221
188
  end
222
189
 
223
- initializer "active_record.warn_on_records_fetched_greater_than" do
224
- if config.active_record.warn_on_records_fetched_greater_than
225
- ActiveSupport.on_load(:active_record) do
226
- require "active_record/relation/record_fetch_warning"
227
- end
228
- end
229
- end
230
-
231
- SQLITE3_PRODUCTION_WARN = "You are running SQLite in production, this is generally not recommended."\
232
- " You can disable this warning by setting \"config.active_record.sqlite3_production_warning=false\"."
233
- initializer "active_record.sqlite3_production_warning" do
234
- if config.active_record.sqlite3_production_warning && Rails.env.production?
235
- ActiveSupport.on_load(:active_record_sqlite3adapter) do
236
- Rails.logger.warn(SQLITE3_PRODUCTION_WARN)
237
- end
238
- end
239
- end
240
-
241
190
  initializer "active_record.sqlite3_adapter_strict_strings_by_default" do
242
191
  config.after_initialize do
243
192
  if config.active_record.sqlite3_adapter_strict_strings_by_default
@@ -248,6 +197,16 @@ To keep using the current cache store, you can turn off cache versioning entirel
248
197
  end
249
198
  end
250
199
 
200
+ initializer "active_record.postgresql_adapter_decode_dates" do
201
+ config.after_initialize do
202
+ if config.active_record.postgresql_adapter_decode_dates
203
+ ActiveSupport.on_load(:active_record_postgresqladapter) do
204
+ self.decode_dates = true
205
+ end
206
+ end
207
+ end
208
+ end
209
+
251
210
  initializer "active_record.set_configs" do |app|
252
211
  configs = app.config.active_record
253
212
 
@@ -273,10 +232,12 @@ To keep using the current cache store, you can turn off cache versioning entirel
273
232
  :query_log_tags,
274
233
  :query_log_tags_format,
275
234
  :cache_query_log_tags,
276
- :sqlite3_production_warning,
235
+ :query_log_tags_prepend_comment,
277
236
  :sqlite3_adapter_strict_strings_by_default,
278
237
  :check_schema_cache_dump_version,
279
- :use_schema_cache_dump
238
+ :use_schema_cache_dump,
239
+ :postgresql_adapter_decode_dates,
240
+ :use_legacy_signed_id_verifier,
280
241
  )
281
242
 
282
243
  configs_used_in_other_initializers.each do |k, v|
@@ -318,6 +279,13 @@ To keep using the current cache store, you can turn off cache versioning entirel
318
279
  end
319
280
  end
320
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
+
321
289
  initializer "active_record.set_reloader_hooks" do
322
290
  ActiveSupport.on_load(:active_record) do
323
291
  ActiveSupport::Reloader.before_class_unload do
@@ -332,6 +300,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
332
300
  initializer "active_record.set_executor_hooks" do
333
301
  ActiveRecord::QueryCache.install_executor_hooks
334
302
  ActiveRecord::AsynchronousQueriesTracker.install_executor_hooks
303
+ ActiveRecord::ConnectionAdapters::ConnectionPool.install_executor_hooks
335
304
  end
336
305
 
337
306
  initializer "active_record.add_watchable_files" do |app|
@@ -362,9 +331,22 @@ To keep using the current cache store, you can turn off cache versioning entirel
362
331
  end
363
332
  end
364
333
 
365
- initializer "active_record.set_signed_id_verifier_secret" do
366
- ActiveSupport.on_load(:active_record) do
367
- 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}"
368
350
  end
369
351
  end
370
352
 
@@ -409,12 +391,13 @@ To keep using the current cache store, you can turn off cache versioning entirel
409
391
  config.after_initialize do
410
392
  if app.config.active_record.query_log_tags_enabled
411
393
  ActiveRecord.query_transformers << ActiveRecord::QueryLogs
412
- ActiveRecord::QueryLogs.taggings.merge!(
394
+ ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
413
395
  application: Rails.application.class.name.split("::").first,
414
396
  pid: -> { Process.pid.to_s },
415
397
  socket: ->(context) { context[:connection].pool.db_config.socket },
416
398
  db_host: ->(context) { context[:connection].pool.db_config.host },
417
- database: ->(context) { context[:connection].pool.db_config.database }
399
+ database: ->(context) { context[:connection].pool.db_config.database },
400
+ source_location: -> { QueryLogs.query_source_location }
418
401
  )
419
402
  ActiveRecord.disable_prepared_statements = true
420
403
 
@@ -423,12 +406,16 @@ To keep using the current cache store, you can turn off cache versioning entirel
423
406
  end
424
407
 
425
408
  if app.config.active_record.query_log_tags_format
426
- 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
427
410
  end
428
411
 
429
412
  if app.config.active_record.cache_query_log_tags
430
413
  ActiveRecord::QueryLogs.cache_query_log_tags = true
431
414
  end
415
+
416
+ if app.config.active_record.query_log_tags_prepend_comment
417
+ ActiveRecord::QueryLogs.prepend_comment = true
418
+ end
432
419
  end
433
420
  end
434
421
  end