activerecord 7.0.0 → 7.1.0

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1607 -1040
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  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 +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  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 +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +345 -219
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +3 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +358 -57
  83. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
  85. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  89. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  92. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  93. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  94. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  95. data/lib/active_record/connection_adapters.rb +3 -1
  96. data/lib/active_record/connection_handling.rb +73 -96
  97. data/lib/active_record/core.rb +136 -148
  98. data/lib/active_record/counter_cache.rb +46 -25
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  100. data/lib/active_record/database_configurations/database_config.rb +9 -3
  101. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  102. data/lib/active_record/database_configurations/url_config.rb +17 -11
  103. data/lib/active_record/database_configurations.rb +87 -34
  104. data/lib/active_record/delegated_type.rb +9 -4
  105. data/lib/active_record/deprecator.rb +7 -0
  106. data/lib/active_record/destroy_association_async_job.rb +2 -0
  107. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  108. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  109. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  110. data/lib/active_record/encryption/config.rb +25 -1
  111. data/lib/active_record/encryption/configurable.rb +13 -14
  112. data/lib/active_record/encryption/context.rb +10 -3
  113. data/lib/active_record/encryption/contexts.rb +8 -4
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  116. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  118. data/lib/active_record/encryption/encryptor.rb +7 -7
  119. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  120. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
  121. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  122. data/lib/active_record/encryption/key_generator.rb +12 -1
  123. data/lib/active_record/encryption/message.rb +1 -1
  124. data/lib/active_record/encryption/message_serializer.rb +2 -0
  125. data/lib/active_record/encryption/properties.rb +4 -4
  126. data/lib/active_record/encryption/scheme.rb +20 -23
  127. data/lib/active_record/encryption.rb +1 -0
  128. data/lib/active_record/enum.rb +114 -27
  129. data/lib/active_record/errors.rb +108 -15
  130. data/lib/active_record/explain.rb +23 -3
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  133. data/lib/active_record/fixture_set/render_context.rb +2 -0
  134. data/lib/active_record/fixture_set/table_row.rb +29 -8
  135. data/lib/active_record/fixtures.rb +121 -73
  136. data/lib/active_record/future_result.rb +30 -5
  137. data/lib/active_record/gem_version.rb +2 -2
  138. data/lib/active_record/inheritance.rb +30 -16
  139. data/lib/active_record/insert_all.rb +55 -8
  140. data/lib/active_record/integration.rb +10 -10
  141. data/lib/active_record/internal_metadata.rb +118 -30
  142. data/lib/active_record/locking/optimistic.rb +32 -18
  143. data/lib/active_record/locking/pessimistic.rb +8 -5
  144. data/lib/active_record/log_subscriber.rb +39 -17
  145. data/lib/active_record/marshalling.rb +56 -0
  146. data/lib/active_record/message_pack.rb +124 -0
  147. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  148. data/lib/active_record/middleware/database_selector.rb +18 -13
  149. data/lib/active_record/middleware/shard_selector.rb +7 -5
  150. data/lib/active_record/migration/command_recorder.rb +104 -9
  151. data/lib/active_record/migration/compatibility.rb +158 -64
  152. data/lib/active_record/migration/default_strategy.rb +23 -0
  153. data/lib/active_record/migration/execution_strategy.rb +19 -0
  154. data/lib/active_record/migration.rb +271 -117
  155. data/lib/active_record/model_schema.rb +82 -50
  156. data/lib/active_record/nested_attributes.rb +23 -3
  157. data/lib/active_record/normalization.rb +159 -0
  158. data/lib/active_record/persistence.rb +200 -47
  159. data/lib/active_record/promise.rb +84 -0
  160. data/lib/active_record/query_cache.rb +3 -21
  161. data/lib/active_record/query_logs.rb +87 -51
  162. data/lib/active_record/query_logs_formatter.rb +41 -0
  163. data/lib/active_record/querying.rb +16 -3
  164. data/lib/active_record/railtie.rb +127 -61
  165. data/lib/active_record/railties/controller_runtime.rb +12 -8
  166. data/lib/active_record/railties/databases.rake +142 -143
  167. data/lib/active_record/railties/job_runtime.rb +23 -0
  168. data/lib/active_record/readonly_attributes.rb +32 -5
  169. data/lib/active_record/reflection.rb +177 -45
  170. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  171. data/lib/active_record/relation/batches.rb +190 -61
  172. data/lib/active_record/relation/calculations.rb +200 -83
  173. data/lib/active_record/relation/delegation.rb +23 -9
  174. data/lib/active_record/relation/finder_methods.rb +77 -16
  175. data/lib/active_record/relation/merger.rb +2 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  177. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  179. data/lib/active_record/relation/predicate_builder.rb +26 -14
  180. data/lib/active_record/relation/query_attribute.rb +25 -1
  181. data/lib/active_record/relation/query_methods.rb +429 -76
  182. data/lib/active_record/relation/spawn_methods.rb +18 -1
  183. data/lib/active_record/relation.rb +98 -41
  184. data/lib/active_record/result.rb +25 -9
  185. data/lib/active_record/runtime_registry.rb +10 -1
  186. data/lib/active_record/sanitization.rb +57 -16
  187. data/lib/active_record/schema.rb +36 -22
  188. data/lib/active_record/schema_dumper.rb +65 -23
  189. data/lib/active_record/schema_migration.rb +68 -33
  190. data/lib/active_record/scoping/default.rb +20 -12
  191. data/lib/active_record/scoping/named.rb +2 -2
  192. data/lib/active_record/scoping.rb +2 -1
  193. data/lib/active_record/secure_password.rb +60 -0
  194. data/lib/active_record/secure_token.rb +21 -3
  195. data/lib/active_record/serialization.rb +5 -0
  196. data/lib/active_record/signed_id.rb +9 -7
  197. data/lib/active_record/store.rb +16 -11
  198. data/lib/active_record/suppressor.rb +3 -1
  199. data/lib/active_record/table_metadata.rb +16 -3
  200. data/lib/active_record/tasks/database_tasks.rb +138 -107
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  203. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  204. data/lib/active_record/test_fixtures.rb +123 -99
  205. data/lib/active_record/timestamp.rb +26 -14
  206. data/lib/active_record/token_for.rb +113 -0
  207. data/lib/active_record/touch_later.rb +11 -6
  208. data/lib/active_record/transactions.rb +39 -13
  209. data/lib/active_record/translation.rb +1 -1
  210. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  211. data/lib/active_record/type/internal/timezone.rb +7 -2
  212. data/lib/active_record/type/serialized.rb +8 -4
  213. data/lib/active_record/type/time.rb +4 -0
  214. data/lib/active_record/validations/absence.rb +1 -1
  215. data/lib/active_record/validations/associated.rb +3 -3
  216. data/lib/active_record/validations/numericality.rb +5 -4
  217. data/lib/active_record/validations/presence.rb +5 -28
  218. data/lib/active_record/validations/uniqueness.rb +50 -5
  219. data/lib/active_record/validations.rb +8 -4
  220. data/lib/active_record/version.rb +1 -1
  221. data/lib/active_record.rb +143 -16
  222. data/lib/arel/errors.rb +10 -0
  223. data/lib/arel/factory_methods.rb +4 -0
  224. data/lib/arel/filter_predications.rb +1 -1
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/binary.rb +6 -1
  227. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  228. data/lib/arel/nodes/cte.rb +36 -0
  229. data/lib/arel/nodes/filter.rb +1 -1
  230. data/lib/arel/nodes/fragments.rb +35 -0
  231. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  232. data/lib/arel/nodes/leading_join.rb +8 -0
  233. data/lib/arel/nodes/node.rb +111 -2
  234. data/lib/arel/nodes/sql_literal.rb +6 -0
  235. data/lib/arel/nodes/table_alias.rb +4 -0
  236. data/lib/arel/nodes.rb +4 -0
  237. data/lib/arel/predications.rb +2 -0
  238. data/lib/arel/table.rb +9 -5
  239. data/lib/arel/visitors/mysql.rb +8 -1
  240. data/lib/arel/visitors/to_sql.rb +81 -17
  241. data/lib/arel/visitors/visitor.rb +2 -2
  242. data/lib/arel.rb +16 -2
  243. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  244. data/lib/rails/generators/active_record/migration.rb +3 -1
  245. data/lib/rails/generators/active_record/model/USAGE +113 -0
  246. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  247. metadata +50 -15
  248. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  249. data/lib/active_record/null_relation.rb +0 -63
@@ -1,13 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/module/attribute_accessors_per_thread"
4
+ require "active_record/query_logs_formatter"
4
5
 
5
6
  module ActiveRecord
6
7
  # = Active Record Query Logs
7
8
  #
8
- # Automatically tag SQL queries with runtime information.
9
+ # Automatically append comments to SQL queries with runtime information tags. This can be used to trace troublesome
10
+ # SQL statements back to the application code that generated these statements.
9
11
  #
10
- # Default tags available for use:
12
+ # Query logs can be enabled via \Rails configuration in <tt>config/application.rb</tt> or an initializer:
13
+ #
14
+ # config.active_record.query_log_tags_enabled = true
15
+ #
16
+ # By default the name of the application, the name and action of the controller, or the name of the job are logged.
17
+ # The default format is {SQLCommenter}[https://open-telemetry.github.io/opentelemetry-sqlcommenter/].
18
+ # The tags shown in a query comment can be configured via \Rails configuration:
19
+ #
20
+ # config.active_record.query_log_tags = [ :application, :controller, :action, :job ]
21
+ #
22
+ # Active Record defines default tags available for use:
11
23
  #
12
24
  # * +application+
13
25
  # * +pid+
@@ -15,46 +27,40 @@ module ActiveRecord
15
27
  # * +db_host+
16
28
  # * +database+
17
29
  #
18
- # _Action Controller and Active Job tags are also defined when used in Rails:_
30
+ # Action Controller adds default tags when loaded:
19
31
  #
20
32
  # * +controller+
21
33
  # * +action+
22
- # * +job+
23
- #
24
- # The tags used in a query can be configured directly:
34
+ # * +namespaced_controller+
25
35
  #
26
- # ActiveRecord::QueryLogs.tags = [ :application, :controller, :action, :job ]
36
+ # Active Job adds default tags when loaded:
27
37
  #
28
- # or via Rails configuration:
38
+ # * +job+
29
39
  #
30
- # config.active_record.query_log_tags = [ :application, :controller, :action, :job ]
40
+ # New comment tags can be defined by adding them in a +Hash+ to the tags +Array+. Tags can have dynamic content by
41
+ # setting a +Proc+ or lambda value in the +Hash+, and can reference any value stored by \Rails in the +context+ object.
42
+ # ActiveSupport::CurrentAttributes can be used to store application values. Tags with +nil+ values are
43
+ # omitted from the query comment.
31
44
  #
32
- # To add new comment tags, add a hash to the tags array containing the keys and values you
33
- # want to add to the comment. Dynamic content can be created by setting a proc or lambda value in a hash,
34
- # and can reference any value stored in the +context+ object.
45
+ # Escaping is performed on the string returned, however untrusted user input should not be used.
35
46
  #
36
47
  # Example:
37
48
  #
38
- # tags = [
39
- # :application,
40
- # {
41
- # custom_tag: ->(context) { context[:controller]&.controller_name },
42
- # custom_value: -> { Custom.value },
43
- # }
44
- # ]
45
- # ActiveRecord::QueryLogs.tags = tags
46
- #
47
- # The QueryLogs +context+ can be manipulated via the +ActiveSupport::ExecutionContext.set+ method.
48
- #
49
- # Temporary updates limited to the execution of a block:
50
- #
51
- # ActiveSupport::ExecutionContext.set(foo: Bar.new) do
52
- # posts = Post.all
53
- # end
54
- #
55
- # Direct updates to a context value:
56
- #
57
- # ActiveSupport::ExecutionContext[:foo] = Bar.new
49
+ # config.active_record.query_log_tags = [
50
+ # :namespaced_controller,
51
+ # :action,
52
+ # :job,
53
+ # {
54
+ # request_id: ->(context) { context[:controller]&.request&.request_id },
55
+ # job_id: ->(context) { context[:job]&.job_id },
56
+ # tenant_id: -> { Current.tenant&.id },
57
+ # static: "value",
58
+ # },
59
+ # ]
60
+ #
61
+ # By default the name of the application, the name and action of the controller, or the name of the job are logged
62
+ # using the {SQLCommenter}[https://open-telemetry.github.io/opentelemetry-sqlcommenter/] format. This can be changed
63
+ # via {config.active_record.query_log_tags_format}[https://guides.rubyonrails.org/configuring.html#config-active-record-query-log-tags-format]
58
64
  #
59
65
  # Tag comments can be prepended to the query:
60
66
  #
@@ -63,59 +69,88 @@ module ActiveRecord
63
69
  # For applications where the content will not change during the lifetime of
64
70
  # the request or job execution, the tags can be cached for reuse in every query:
65
71
  #
66
- # ActiveRecord::QueryLogs.cache_query_log_tags = true
67
- #
68
- # This option can be set during application configuration or in a Rails initializer:
69
- #
70
72
  # config.active_record.cache_query_log_tags = true
71
73
  module QueryLogs
72
74
  mattr_accessor :taggings, instance_accessor: false, default: {}
73
75
  mattr_accessor :tags, instance_accessor: false, default: [ :application ]
74
76
  mattr_accessor :prepend_comment, instance_accessor: false, default: false
75
77
  mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
78
+ mattr_accessor :tags_formatter, instance_accessor: false
76
79
  thread_mattr_accessor :cached_comment, instance_accessor: false
77
80
 
78
81
  class << self
79
- def call(sql) # :nodoc:
80
- if prepend_comment
81
- "#{self.comment} #{sql}"
82
+ def call(sql, connection) # :nodoc:
83
+ comment = self.comment(connection)
84
+
85
+ if comment.blank?
86
+ sql
87
+ elsif prepend_comment
88
+ "#{comment} #{sql}"
82
89
  else
83
- "#{sql} #{self.comment}"
84
- end.strip
90
+ "#{sql} #{comment}"
91
+ end
85
92
  end
86
93
 
87
94
  def clear_cache # :nodoc:
88
95
  self.cached_comment = nil
89
96
  end
90
97
 
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
109
+ end
110
+
91
111
  ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
92
112
 
93
113
  private
94
114
  # Returns an SQL comment +String+ containing the query log tags.
95
115
  # Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
96
- def comment
116
+ def comment(connection)
97
117
  if cache_query_log_tags
98
- self.cached_comment ||= uncached_comment
118
+ self.cached_comment ||= uncached_comment(connection)
99
119
  else
100
- uncached_comment
120
+ uncached_comment(connection)
101
121
  end
102
122
  end
103
123
 
104
- def uncached_comment
105
- content = tag_content
124
+ def formatter
125
+ self.tags_formatter || self.update_formatter(:legacy)
126
+ end
127
+
128
+ def uncached_comment(connection)
129
+ content = tag_content(connection)
130
+
106
131
  if content.present?
107
132
  "/*#{escape_sql_comment(content)}*/"
108
133
  end
109
134
  end
110
135
 
111
136
  def escape_sql_comment(content)
112
- content.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
137
+ # Sanitize a string to appear within a SQL comment
138
+ # For compatibility, this also surrounding "/*+", "/*", and "*/"
139
+ # characters, possibly with single surrounding space.
140
+ # Then follows that by replacing any internal "*/" or "/ *" with
141
+ # "* /" or "/ *"
142
+ comment = content.to_s.dup
143
+ comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
144
+ comment.gsub!("*/", "* /")
145
+ comment.gsub!("/*", "/ *")
146
+ comment
113
147
  end
114
148
 
115
- def tag_content
149
+ def tag_content(connection)
116
150
  context = ActiveSupport::ExecutionContext.to_h
151
+ context[:connection] ||= connection
117
152
 
118
- tags.flat_map { |i| [*i] }.filter_map do |tag|
153
+ pairs = tags.flat_map { |i| [*i] }.filter_map do |tag|
119
154
  key, handler = tag
120
155
  handler ||= taggings[key]
121
156
 
@@ -130,8 +165,9 @@ module ActiveRecord
130
165
  else
131
166
  handler
132
167
  end
133
- "#{key}:#{val}" unless val.nil?
134
- end.join(",")
168
+ [key, val] unless val.nil?
169
+ end
170
+ self.formatter.format(pairs)
135
171
  end
136
172
  end
137
173
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
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
22
+ end
23
+ end
24
+
25
+ class SQLCommenter < LegacyFormatter # :nodoc:
26
+ def initialize
27
+ @key_value_separator = "="
28
+ end
29
+
30
+ def format(pairs)
31
+ pairs.sort_by!(&:first)
32
+ super
33
+ end
34
+
35
+ private
36
+ def format_value(value)
37
+ "'#{ERB::Util.url_encode(value)}'"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -12,12 +12,13 @@ module ActiveRecord
12
12
  :create_or_find_by, :create_or_find_by!,
13
13
  :destroy_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by,
14
14
  :find_each, :find_in_batches, :in_batches,
15
- :select, :reselect, :order, :in_order_of, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins,
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, :strict_loading, :excluding, :without
20
+ :pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with,
21
+ :async_count, :async_average, :async_minimum, :async_maximum, :async_sum, :async_pluck, :async_pick,
21
22
  ].freeze # :nodoc:
22
23
  delegate(*QUERYING_METHODS, to: :all)
23
24
 
@@ -39,7 +40,7 @@ module ActiveRecord
39
40
  # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
40
41
  # # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "author"=>"Quentin"}>, ...]
41
42
  #
42
- # You can use the same string replacement techniques as you can with <tt>ActiveRecord::QueryMethods#where</tt>:
43
+ # You can use the same string replacement techniques as you can with ActiveRecord::QueryMethods#where :
43
44
  #
44
45
  # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
45
46
  # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
@@ -50,6 +51,13 @@ module ActiveRecord
50
51
  _load_from_sql(_query_by_sql(sql, binds, preparable: preparable), &block)
51
52
  end
52
53
 
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|
57
+ _load_from_sql(result, &block)
58
+ end
59
+ end
60
+
53
61
  def _query_by_sql(sql, binds = [], preparable: nil, async: false) # :nodoc:
54
62
  connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable, async: async)
55
63
  end
@@ -93,5 +101,10 @@ module ActiveRecord
93
101
  def count_by_sql(sql)
94
102
  connection.select_value(sanitize_sql(sql), "#{name} Count").to_i
95
103
  end
104
+
105
+ # Same as <tt>#count_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
106
+ def async_count_by_sql(sql)
107
+ connection.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
108
+ end
96
109
  end
97
110
  end
@@ -34,7 +34,11 @@ module ActiveRecord
34
34
  config.active_record.sqlite3_production_warning = true
35
35
  config.active_record.query_log_tags_enabled = false
36
36
  config.active_record.query_log_tags = [ :application ]
37
+ config.active_record.query_log_tags_format = :legacy
37
38
  config.active_record.cache_query_log_tags = false
39
+ config.active_record.raise_on_assign_to_attr_readonly = false
40
+ config.active_record.belongs_to_required_validates_foreign_key = true
41
+ config.active_record.generate_secure_token_on = :create
38
42
 
39
43
  config.active_record.queues = ActiveSupport::InheritableOptions.new
40
44
 
@@ -63,7 +67,7 @@ module ActiveRecord
63
67
  unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
64
68
  console = ActiveSupport::Logger.new(STDERR)
65
69
  console.level = Rails.logger.level
66
- Rails.logger.extend ActiveSupport::Logger.broadcast console
70
+ Rails.logger.broadcast_to(console)
67
71
  end
68
72
  ActiveRecord.verbose_query_logs = false
69
73
  end
@@ -72,12 +76,22 @@ module ActiveRecord
72
76
  require "active_record/base"
73
77
  end
74
78
 
79
+ initializer "active_record.deprecator", before: :load_environment_config do |app|
80
+ app.deprecators[:active_record] = ActiveRecord.deprecator
81
+ end
82
+
75
83
  initializer "active_record.initialize_timezone" do
76
84
  ActiveSupport.on_load(:active_record) do
77
85
  self.time_zone_aware_attributes = true
78
86
  end
79
87
  end
80
88
 
89
+ initializer "active_record.postgresql_time_zone_aware_types" do
90
+ ActiveSupport.on_load(:active_record_postgresqladapter) do
91
+ ActiveRecord::Base.time_zone_aware_types << :timestamptz
92
+ end
93
+ end
94
+
81
95
  initializer "active_record.logger" do
82
96
  ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
83
97
  end
@@ -94,23 +108,7 @@ module ActiveRecord
94
108
  end
95
109
  end
96
110
 
97
- initializer "active_record.database_selector" do
98
- if options = config.active_record.database_selector
99
- resolver = config.active_record.database_resolver
100
- operations = config.active_record.database_resolver_context
101
- config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options
102
- end
103
- end
104
-
105
- initializer "active_record.shard_selector" do
106
- if resolver = config.active_record.shard_resolver
107
- options = config.active_record.shard_selector || {}
108
-
109
- config.app_middleware.use ActiveRecord::Middleware::ShardSelector, resolver, options
110
- end
111
- end
112
-
113
- initializer "Check for cache versioning support" do
111
+ initializer "active_record.cache_versioning_support" do
114
112
  config.after_initialize do |app|
115
113
  ActiveSupport.on_load(:active_record) do
116
114
  if app.config.active_record.cache_versioning && Rails.cache
@@ -135,9 +133,15 @@ To keep using the current cache store, you can turn off cache versioning entirel
135
133
  end
136
134
  end
137
135
 
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
+
138
140
  initializer "active_record.check_schema_cache_dump" do
139
141
  check_schema_cache_dump_version = config.active_record.check_schema_cache_dump_version
140
142
 
143
+ ActiveRecord::ConnectionAdapters::SchemaReflection.check_schema_cache_dump_version = check_schema_cache_dump_version
144
+
141
145
  if config.active_record.use_schema_cache_dump && !config.active_record.lazily_load_schema_cache
142
146
  config.after_initialize do |app|
143
147
  ActiveSupport.on_load(:active_record) do
@@ -145,10 +149,10 @@ To keep using the current cache store, you can turn off cache versioning entirel
145
149
 
146
150
  filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
147
151
  db_config.name,
148
- schema_cache_path: db_config&.schema_cache_path
152
+ schema_cache_path: db_config.schema_cache_path
149
153
  )
150
154
 
151
- cache = ActiveRecord::ConnectionAdapters::SchemaCache.load_from(filename)
155
+ cache = ActiveRecord::ConnectionAdapters::SchemaCache._load_from(filename)
152
156
  next if cache.nil?
153
157
 
154
158
  if check_schema_cache_dump_version
@@ -160,34 +164,47 @@ To keep using the current cache store, you can turn off cache versioning entirel
160
164
  end
161
165
  next if current_version.nil?
162
166
 
163
- if cache.version != current_version
164
- warn "Ignoring #{filename} because it has expired. The current schema version is #{current_version}, but the one in the schema cache file is #{cache.version}."
167
+ if cache.schema_version != current_version
168
+ 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}."
165
169
  next
166
170
  end
167
171
  end
168
172
 
169
173
  Rails.logger.info("Using schema cache file #{filename}")
170
- connection_pool.set_schema_cache(cache)
174
+ connection_pool.schema_reflection.set_schema_cache(cache)
171
175
  end
172
176
  end
173
177
  end
174
178
  end
175
179
 
176
180
  initializer "active_record.define_attribute_methods" do |app|
181
+ # For resiliency, it is critical that a Rails application should be
182
+ # able to boot without depending on the database (or any other service)
183
+ # being responsive.
184
+ #
185
+ # Otherwise a bad deploy adding a lot of load on the database may require to
186
+ # entirely shutdown the application so the database can recover before a fixed
187
+ # version can be deployed again.
188
+ #
189
+ # This is why this initializer tries hard not to query the database, and if it
190
+ # does, it makes sure to rescue any possible database error.
191
+ check_schema_cache_dump_version = config.active_record.check_schema_cache_dump_version
177
192
  config.after_initialize do
178
193
  ActiveSupport.on_load(:active_record) do
179
- if app.config.eager_load
194
+ # In development and test we shouldn't eagerly define attribute methods because
195
+ # db:test:prepare will trigger later and might change the schema.
196
+ #
197
+ # Additionally if `check_schema_cache_dump_version` is enabled (which is the default),
198
+ # loading the schema cache dump trigger a database connection to compare the schema
199
+ # versions.
200
+ # This means the attribute methods will be lazily defined whent the model is accessed,
201
+ # likely as part of the first few requests or jobs. This isn't good for performance
202
+ # but we unfortunately have to arbitrate between resiliency and performance, and chose
203
+ # resiliency.
204
+ if !check_schema_cache_dump_version && app.config.eager_load && !Rails.env.local?
180
205
  begin
181
206
  descendants.each do |model|
182
- # If the schema cache was loaded from a dump, we can use it without connecting
183
- schema_cache = model.connection_pool.schema_cache
184
-
185
- # If there's no connection yet, we avoid connecting.
186
- schema_cache ||= model.connected? && model.connection.schema_cache
187
-
188
- # If the schema cache doesn't have the columns
189
- # hash for the model cached, `define_attribute_methods` would trigger a query.
190
- if schema_cache && schema_cache.columns_hash?(model.table_name)
207
+ if model.connection_pool.schema_reflection.cached?(model.table_name)
191
208
  model.define_attribute_methods
192
209
  end
193
210
  end
@@ -220,6 +237,16 @@ To keep using the current cache store, you can turn off cache versioning entirel
220
237
  end
221
238
  end
222
239
 
240
+ initializer "active_record.sqlite3_adapter_strict_strings_by_default" do
241
+ config.after_initialize do
242
+ if config.active_record.sqlite3_adapter_strict_strings_by_default
243
+ ActiveSupport.on_load(:active_record_sqlite3adapter) do
244
+ self.strict_strings_by_default = true
245
+ end
246
+ end
247
+ end
248
+ end
249
+
223
250
  initializer "active_record.set_configs" do |app|
224
251
  configs = app.config.active_record
225
252
 
@@ -244,8 +271,10 @@ To keep using the current cache store, you can turn off cache versioning entirel
244
271
  :shard_resolver,
245
272
  :query_log_tags_enabled,
246
273
  :query_log_tags,
274
+ :query_log_tags_format,
247
275
  :cache_query_log_tags,
248
276
  :sqlite3_production_warning,
277
+ :sqlite3_adapter_strict_strings_by_default,
249
278
  :check_schema_cache_dump_version,
250
279
  :use_schema_cache_dump
251
280
  )
@@ -270,21 +299,23 @@ To keep using the current cache store, you can turn off cache versioning entirel
270
299
  # and then establishes the connection.
271
300
  initializer "active_record.initialize_database" do
272
301
  ActiveSupport.on_load(:active_record) do
273
- if ActiveRecord.legacy_connection_handling
274
- self.connection_handlers = { ActiveRecord.writing_role => ActiveRecord::Base.default_connection_handler }
275
- end
276
302
  self.configurations = Rails.application.config.database_configuration
277
303
 
278
304
  establish_connection
279
305
  end
280
306
  end
281
307
 
282
- # Expose database runtime to controller for logging.
308
+ # Expose database runtime for logging.
283
309
  initializer "active_record.log_runtime" do
284
310
  require "active_record/railties/controller_runtime"
285
311
  ActiveSupport.on_load(:action_controller) do
286
312
  include ActiveRecord::Railties::ControllerRuntime
287
313
  end
314
+
315
+ require "active_record/railties/job_runtime"
316
+ ActiveSupport.on_load(:active_job) do
317
+ include ActiveRecord::Railties::JobRuntime
318
+ end
288
319
  end
289
320
 
290
321
  initializer "active_record.set_reloader_hooks" do
@@ -292,7 +323,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
292
323
  ActiveSupport::Reloader.before_class_unload do
293
324
  if ActiveRecord::Base.connected?
294
325
  ActiveRecord::Base.clear_cache!
295
- ActiveRecord::Base.clear_reloadable_connections!
326
+ ActiveRecord::Base.connection_handler.clear_reloadable_connections!(:all)
296
327
  end
297
328
  end
298
329
  end
@@ -319,8 +350,8 @@ To keep using the current cache store, you can turn off cache versioning entirel
319
350
  # this connection is trivial: the rest of the pool would need to be
320
351
  # populated anyway.
321
352
 
322
- clear_active_connections!
323
- flush_idle_connections!
353
+ connection_handler.clear_active_connections!(:all)
354
+ connection_handler.flush_idle_connections!(:all)
324
355
  end
325
356
  end
326
357
  end
@@ -337,18 +368,32 @@ To keep using the current cache store, you can turn off cache versioning entirel
337
368
  end
338
369
  end
339
370
 
371
+ initializer "active_record.generated_token_verifier" do
372
+ config.after_initialize do |app|
373
+ ActiveSupport.on_load(:active_record) do
374
+ self.generated_token_verifier ||= app.message_verifier("active_record/token_for")
375
+ end
376
+ end
377
+ end
378
+
340
379
  initializer "active_record_encryption.configuration" do |app|
341
- ActiveRecord::Encryption.configure \
342
- primary_key: app.credentials.dig(:active_record_encryption, :primary_key),
343
- deterministic_key: app.credentials.dig(:active_record_encryption, :deterministic_key),
344
- key_derivation_salt: app.credentials.dig(:active_record_encryption, :key_derivation_salt),
345
- **config.active_record.encryption
380
+ auto_filtered_parameters = ActiveRecord::Encryption::AutoFilteredParameters.new(app)
346
381
 
347
- ActiveSupport.on_load(:active_record) do
348
- # Support extended queries for deterministic attributes and validations
349
- if ActiveRecord::Encryption.config.extend_queries
350
- ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support
351
- ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support
382
+ config.after_initialize do |app|
383
+ ActiveRecord::Encryption.configure \
384
+ primary_key: app.credentials.dig(:active_record_encryption, :primary_key),
385
+ deterministic_key: app.credentials.dig(:active_record_encryption, :deterministic_key),
386
+ key_derivation_salt: app.credentials.dig(:active_record_encryption, :key_derivation_salt),
387
+ **config.active_record.encryption
388
+
389
+ auto_filtered_parameters.enable if ActiveRecord::Encryption.config.add_to_filter_parameters
390
+
391
+ ActiveSupport.on_load(:active_record) do
392
+ # Support extended queries for deterministic attributes and validations
393
+ if ActiveRecord::Encryption.config.extend_queries
394
+ ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support
395
+ ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support
396
+ end
352
397
  end
353
398
  end
354
399
 
@@ -358,13 +403,6 @@ To keep using the current cache store, you can turn off cache versioning entirel
358
403
  ActiveRecord::Fixture.prepend ActiveRecord::Encryption::EncryptedFixtures
359
404
  end
360
405
  end
361
-
362
- # Filtered params
363
- ActiveSupport.on_load(:action_controller) do
364
- if ActiveRecord::Encryption.config.add_to_filter_parameters
365
- ActiveRecord::Encryption.install_auto_filtered_parameters(app)
366
- end
367
- end
368
406
  end
369
407
 
370
408
  initializer "active_record.query_log_tags_config" do |app|
@@ -373,21 +411,49 @@ To keep using the current cache store, you can turn off cache versioning entirel
373
411
  ActiveRecord.query_transformers << ActiveRecord::QueryLogs
374
412
  ActiveRecord::QueryLogs.taggings.merge!(
375
413
  application: Rails.application.class.name.split("::").first,
376
- pid: -> { Process.pid },
377
- socket: -> { ActiveRecord::Base.connection_db_config.socket },
378
- db_host: -> { ActiveRecord::Base.connection_db_config.host },
379
- database: -> { ActiveRecord::Base.connection_db_config.database }
414
+ pid: -> { Process.pid.to_s },
415
+ socket: ->(context) { context[:connection].pool.db_config.socket },
416
+ db_host: ->(context) { context[:connection].pool.db_config.host },
417
+ database: ->(context) { context[:connection].pool.db_config.database }
380
418
  )
419
+ ActiveRecord.disable_prepared_statements = true
381
420
 
382
421
  if app.config.active_record.query_log_tags.present?
383
422
  ActiveRecord::QueryLogs.tags = app.config.active_record.query_log_tags
384
423
  end
385
424
 
425
+ if app.config.active_record.query_log_tags_format
426
+ ActiveRecord::QueryLogs.update_formatter(app.config.active_record.query_log_tags_format)
427
+ end
428
+
386
429
  if app.config.active_record.cache_query_log_tags
387
430
  ActiveRecord::QueryLogs.cache_query_log_tags = true
388
431
  end
389
432
  end
390
433
  end
391
434
  end
435
+
436
+ initializer "active_record.unregister_current_scopes_on_unload" do |app|
437
+ config.after_initialize do
438
+ if app.config.reloading_enabled?
439
+ Rails.autoloaders.main.on_unload do |_cpath, value, _abspath|
440
+ # Conditions are written this way to be robust against custom
441
+ # implementations of value#is_a? or value#<.
442
+ if Class === value && ActiveRecord::Base > value
443
+ value.current_scope = nil
444
+ end
445
+ end
446
+ end
447
+ end
448
+ end
449
+
450
+ initializer "active_record.message_pack" do
451
+ ActiveSupport.on_load(:message_pack) do
452
+ ActiveSupport.on_load(:active_record) do
453
+ require "active_record/message_pack"
454
+ ActiveRecord::MessagePack::Extensions.install(ActiveSupport::MessagePack::CacheSerializer)
455
+ end
456
+ end
457
+ end
392
458
  end
393
459
  end