activerecord 7.0.8.7 → 7.2.3

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 (283) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +781 -1777
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +30 -30
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +31 -23
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +40 -9
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +35 -21
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +4 -3
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -33
  47. data/lib/active_record/attributes.rb +96 -71
  48. data/lib/active_record/autosave_association.rb +81 -39
  49. data/lib/active_record/base.rb +11 -7
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +343 -91
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +229 -64
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +142 -12
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +539 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +289 -128
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +60 -55
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +108 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +371 -64
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +374 -203
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -45
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +51 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +298 -113
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +101 -105
  110. data/lib/active_record/core.rb +273 -178
  111. data/lib/active_record/counter_cache.rb +69 -35
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +56 -27
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +46 -22
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +48 -13
  130. data/lib/active_record/encryption/encryptor.rb +35 -19
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +130 -28
  143. data/lib/active_record/errors.rb +154 -34
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +48 -10
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +236 -118
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +96 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +35 -10
  178. data/lib/active_record/railtie.rb +131 -87
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +147 -155
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +270 -108
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +97 -21
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +3 -2
  196. data/lib/active_record/relation/query_methods.rb +585 -109
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +15 -21
  200. data/lib/active_record/relation.rb +592 -92
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +90 -23
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +33 -11
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +23 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +108 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +3 -1
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/crud.rb +2 -0
  248. data/lib/arel/delete_manager.rb +5 -0
  249. data/lib/arel/errors.rb +10 -0
  250. data/lib/arel/factory_methods.rb +4 -0
  251. data/lib/arel/nodes/binary.rb +6 -7
  252. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  253. data/lib/arel/nodes/cte.rb +36 -0
  254. data/lib/arel/nodes/delete_statement.rb +4 -2
  255. data/lib/arel/nodes/fragments.rb +35 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  257. data/lib/arel/nodes/leading_join.rb +8 -0
  258. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  259. data/lib/arel/nodes/node.rb +115 -5
  260. data/lib/arel/nodes/sql_literal.rb +13 -0
  261. data/lib/arel/nodes/table_alias.rb +4 -0
  262. data/lib/arel/nodes/update_statement.rb +4 -2
  263. data/lib/arel/nodes.rb +6 -2
  264. data/lib/arel/predications.rb +3 -1
  265. data/lib/arel/select_manager.rb +7 -3
  266. data/lib/arel/table.rb +9 -5
  267. data/lib/arel/tree_manager.rb +8 -3
  268. data/lib/arel/update_manager.rb +7 -1
  269. data/lib/arel/visitors/dot.rb +3 -0
  270. data/lib/arel/visitors/mysql.rb +17 -5
  271. data/lib/arel/visitors/postgresql.rb +1 -12
  272. data/lib/arel/visitors/sqlite.rb +25 -0
  273. data/lib/arel/visitors/to_sql.rb +114 -34
  274. data/lib/arel/visitors/visitor.rb +2 -2
  275. data/lib/arel.rb +21 -3
  276. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  277. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  278. data/lib/rails/generators/active_record/migration.rb +3 -1
  279. data/lib/rails/generators/active_record/model/USAGE +113 -0
  280. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  281. metadata +56 -17
  282. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  283. data/lib/active_record/null_relation.rb +0 -63
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class Promise < BasicObject
5
+ undef_method :==, :!, :!=
6
+
7
+ def initialize(future_result, block) # :nodoc:
8
+ @future_result = future_result
9
+ @block = block
10
+ end
11
+
12
+ # Returns whether the associated query is still being executed or not.
13
+ def pending?
14
+ @future_result.pending?
15
+ end
16
+
17
+ # Returns the query result.
18
+ # If the query wasn't completed yet, accessing +#value+ will block until the query completes.
19
+ # If the query failed, +#value+ will raise the corresponding error.
20
+ def value
21
+ return @value if defined? @value
22
+
23
+ result = @future_result.result
24
+ @value = if @block
25
+ @block.call(result)
26
+ else
27
+ result
28
+ end
29
+ end
30
+
31
+ # Returns a new +ActiveRecord::Promise+ that will apply the passed block
32
+ # when the value is accessed:
33
+ #
34
+ # Post.async_pick(:title).then { |title| title.upcase }.value
35
+ # # => "POST TITLE"
36
+ def then(&block)
37
+ Promise.new(@future_result, @block ? @block >> block : block)
38
+ end
39
+
40
+ [:class, :respond_to?, :is_a?].each do |method|
41
+ define_method(method, ::Object.instance_method(method))
42
+ end
43
+
44
+ def inspect # :nodoc:
45
+ "#<ActiveRecord::Promise status=#{status}>"
46
+ end
47
+
48
+ def pretty_print(q) # :nodoc:
49
+ q.text(inspect)
50
+ end
51
+
52
+ private
53
+ def status
54
+ if @future_result.pending?
55
+ :pending
56
+ elsif @future_result.canceled?
57
+ :canceled
58
+ else
59
+ :complete
60
+ end
61
+ end
62
+
63
+ class Complete < self # :nodoc:
64
+ attr_reader :value
65
+
66
+ def initialize(value)
67
+ @value = value
68
+ end
69
+
70
+ def then
71
+ Complete.new(yield @value)
72
+ end
73
+
74
+ def pending?
75
+ false
76
+ end
77
+
78
+ private
79
+ def status
80
+ :complete
81
+ end
82
+ end
83
+ end
84
+ end
@@ -8,7 +8,13 @@ module ActiveRecord
8
8
  # If it's not, it will execute the given block.
9
9
  def cache(&block)
10
10
  if connected? || !configurations.empty?
11
- connection.cache(&block)
11
+ pool = connection_pool
12
+ was_enabled = pool.query_cache_enabled
13
+ begin
14
+ pool.enable_query_cache(&block)
15
+ ensure
16
+ pool.clear_query_cache unless was_enabled
17
+ end
12
18
  else
13
19
  yield
14
20
  end
@@ -16,9 +22,12 @@ module ActiveRecord
16
22
 
17
23
  # Disable the query cache within the block if Active Record is configured.
18
24
  # If it's not, it will execute the given block.
19
- def uncached(&block)
25
+ #
26
+ # Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
27
+ # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
28
+ def uncached(dirties: true, &block)
20
29
  if connected? || !configurations.empty?
21
- connection.uncached(&block)
30
+ connection_pool.disable_query_cache(dirties: dirties, &block)
22
31
  else
23
32
  yield
24
33
  end
@@ -26,32 +35,16 @@ module ActiveRecord
26
35
  end
27
36
 
28
37
  def self.run
29
- pools = []
30
-
31
- if ActiveRecord.legacy_connection_handling
32
- ActiveRecord::Base.connection_handlers.each do |key, handler|
33
- pools.concat(handler.connection_pool_list.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! })
34
- end
35
- else
36
- pools.concat(ActiveRecord::Base.connection_handler.all_connection_pools.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! })
38
+ ActiveRecord::Base.connection_handler.each_connection_pool.reject(&:query_cache_enabled).each do |pool|
39
+ next if pool.db_config&.query_cache == false
40
+ pool.enable_query_cache!
37
41
  end
38
-
39
- pools
40
42
  end
41
43
 
42
44
  def self.complete(pools)
43
- pools.each { |pool| pool.disable_query_cache! }
44
-
45
- if ActiveRecord.legacy_connection_handling
46
- ActiveRecord::Base.connection_handlers.each do |_, handler|
47
- handler.connection_pool_list.each do |pool|
48
- pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
49
- end
50
- end
51
- else
52
- ActiveRecord::Base.connection_handler.all_connection_pools.each do |pool|
53
- pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
54
- end
45
+ pools.each do |pool|
46
+ pool.disable_query_cache!
47
+ pool.clear_query_cache
55
48
  end
56
49
  end
57
50
 
@@ -1,62 +1,71 @@
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+
14
26
  # * +socket+
15
27
  # * +db_host+
16
28
  # * +database+
29
+ # * +source_location+
17
30
  #
18
- # _Action Controller and Active Job tags are also defined when used in Rails:_
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.
19
32
  #
20
- # * +controller+
21
- # * +action+
22
- # * +job+
33
+ # Also see {config.active_record.verbose_query_logs}[https://guides.rubyonrails.org/debugging_rails_applications.html#verbose-query-logs].
23
34
  #
24
- # The tags used in a query can be configured directly:
35
+ # Action Controller adds default tags when loaded:
25
36
  #
26
- # ActiveRecord::QueryLogs.tags = [ :application, :controller, :action, :job ]
37
+ # * +controller+
38
+ # * +action+
39
+ # * +namespaced_controller+
27
40
  #
28
- # or via Rails configuration:
41
+ # Active Job adds default tags when loaded:
29
42
  #
30
- # config.active_record.query_log_tags = [ :application, :controller, :action, :job ]
43
+ # * +job+
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
+ # New comment tags can be defined by adding them in a +Hash+ to the tags +Array+. Tags can have dynamic content by
46
+ # setting a +Proc+ or lambda value in the +Hash+, and can reference any value stored by \Rails in the +context+ object.
47
+ # ActiveSupport::CurrentAttributes can be used to store application values. Tags with +nil+ values are
48
+ # omitted from the query comment.
35
49
  #
36
50
  # Escaping is performed on the string returned, however untrusted user input should not be used.
37
51
  #
38
52
  # Example:
39
53
  #
40
- # tags = [
41
- # :application,
42
- # {
43
- # custom_tag: ->(context) { context[:controller]&.controller_name },
44
- # custom_value: -> { Custom.value },
45
- # }
46
- # ]
47
- # ActiveRecord::QueryLogs.tags = tags
48
- #
49
- # The QueryLogs +context+ can be manipulated via the +ActiveSupport::ExecutionContext.set+ method.
50
- #
51
- # Temporary updates limited to the execution of a block:
52
- #
53
- # ActiveSupport::ExecutionContext.set(foo: Bar.new) do
54
- # posts = Post.all
55
- # end
56
- #
57
- # Direct updates to a context value:
58
- #
59
- # ActiveSupport::ExecutionContext[:foo] = Bar.new
54
+ # config.active_record.query_log_tags = [
55
+ # :namespaced_controller,
56
+ # :action,
57
+ # :job,
58
+ # {
59
+ # request_id: ->(context) { context[:controller]&.request&.request_id },
60
+ # job_id: ->(context) { context[:job]&.job_id },
61
+ # tenant_id: -> { Current.tenant&.id },
62
+ # static: "value",
63
+ # },
64
+ # ]
65
+ #
66
+ # By default the name of the application, the name and action of the controller, or the name of the job are logged
67
+ # using the {SQLCommenter}[https://open-telemetry.github.io/opentelemetry-sqlcommenter/] format. This can be changed
68
+ # via {config.active_record.query_log_tags_format}[https://guides.rubyonrails.org/configuring.html#config-active-record-query-log-tags-format]
60
69
  #
61
70
  # Tag comments can be prepended to the query:
62
71
  #
@@ -65,46 +74,79 @@ module ActiveRecord
65
74
  # For applications where the content will not change during the lifetime of
66
75
  # the request or job execution, the tags can be cached for reuse in every query:
67
76
  #
68
- # ActiveRecord::QueryLogs.cache_query_log_tags = true
69
- #
70
- # This option can be set during application configuration or in a Rails initializer:
71
- #
72
77
  # config.active_record.cache_query_log_tags = true
73
78
  module QueryLogs
74
79
  mattr_accessor :taggings, instance_accessor: false, default: {}
75
80
  mattr_accessor :tags, instance_accessor: false, default: [ :application ]
76
81
  mattr_accessor :prepend_comment, instance_accessor: false, default: false
77
82
  mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
83
+ mattr_accessor :tags_formatter, instance_accessor: false
78
84
  thread_mattr_accessor :cached_comment, instance_accessor: false
79
85
 
80
86
  class << self
81
- def call(sql) # :nodoc:
82
- if prepend_comment
83
- "#{self.comment} #{sql}"
87
+ def call(sql, connection) # :nodoc:
88
+ comment = self.comment(connection)
89
+
90
+ if comment.blank?
91
+ sql
92
+ elsif prepend_comment
93
+ "#{comment} #{sql}"
84
94
  else
85
- "#{sql} #{self.comment}"
86
- end.strip
95
+ "#{sql} #{comment}"
96
+ end
87
97
  end
88
98
 
89
99
  def clear_cache # :nodoc:
90
100
  self.cached_comment = nil
91
101
  end
92
102
 
103
+ # Updates the formatter to be what the passed in format is.
104
+ def update_formatter(format)
105
+ self.tags_formatter =
106
+ case format
107
+ when :legacy
108
+ LegacyFormatter.new
109
+ when :sqlcommenter
110
+ SQLCommenter.new
111
+ else
112
+ raise ArgumentError, "Formatter is unsupported: #{formatter}"
113
+ end
114
+ end
115
+
116
+ if Thread.respond_to?(:each_caller_location)
117
+ def query_source_location # :nodoc:
118
+ Thread.each_caller_location do |location|
119
+ frame = LogSubscriber.backtrace_cleaner.clean_frame(location)
120
+ return frame if frame
121
+ end
122
+ nil
123
+ end
124
+ else
125
+ def query_source_location # :nodoc:
126
+ LogSubscriber.backtrace_cleaner.clean(caller_locations(1).each).first
127
+ end
128
+ end
129
+
93
130
  ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
94
131
 
95
132
  private
96
133
  # Returns an SQL comment +String+ containing the query log tags.
97
134
  # Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
98
- def comment
135
+ def comment(connection)
99
136
  if cache_query_log_tags
100
- self.cached_comment ||= uncached_comment
137
+ self.cached_comment ||= uncached_comment(connection)
101
138
  else
102
- uncached_comment
139
+ uncached_comment(connection)
103
140
  end
104
141
  end
105
142
 
106
- def uncached_comment
107
- content = tag_content
143
+ def formatter
144
+ self.tags_formatter || self.update_formatter(:legacy)
145
+ end
146
+
147
+ def uncached_comment(connection)
148
+ content = tag_content(connection)
149
+
108
150
  if content.present?
109
151
  "/*#{escape_sql_comment(content)}*/"
110
152
  end
@@ -113,7 +155,7 @@ module ActiveRecord
113
155
  def escape_sql_comment(content)
114
156
  # Sanitize a string to appear within a SQL comment
115
157
  # For compatibility, this also surrounding "/*+", "/*", and "*/"
116
- # charcacters, possibly with single surrounding space.
158
+ # characters, possibly with single surrounding space.
117
159
  # Then follows that by replacing any internal "*/" or "/ *" with
118
160
  # "* /" or "/ *"
119
161
  comment = content.to_s.dup
@@ -123,10 +165,11 @@ module ActiveRecord
123
165
  comment
124
166
  end
125
167
 
126
- def tag_content
168
+ def tag_content(connection)
127
169
  context = ActiveSupport::ExecutionContext.to_h
170
+ context[:connection] ||= connection
128
171
 
129
- tags.flat_map { |i| [*i] }.filter_map do |tag|
172
+ pairs = tags.flat_map { |i| [*i] }.filter_map do |tag|
130
173
  key, handler = tag
131
174
  handler ||= taggings[key]
132
175
 
@@ -141,8 +184,9 @@ module ActiveRecord
141
184
  else
142
185
  handler
143
186
  end
144
- "#{key}:#{val}" unless val.nil?
145
- end.join(",")
187
+ [key, val] unless val.nil?
188
+ end
189
+ self.formatter.format(pairs)
146
190
  end
147
191
  end
148
192
  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! { |pair| pair.first.to_s }
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
@@ -10,14 +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
- :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, :with_recursive,
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
21
23
  ].freeze # :nodoc:
22
24
  delegate(*QUERYING_METHODS, to: :all)
23
25
 
@@ -44,14 +46,28 @@ module ActiveRecord
44
46
  # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
45
47
  # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
46
48
  #
47
- # Note that building your own SQL query string from user input may expose your application to
48
- # injection attacks (https://guides.rubyonrails.org/security.html#sql-injection).
49
- def find_by_sql(sql, binds = [], preparable: nil, &block)
50
- _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)
56
+ end
57
+
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, &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|
65
+ _load_from_sql(result, &block)
66
+ end
51
67
  end
52
68
 
53
- def _query_by_sql(sql, binds = [], preparable: nil, async: false) # :nodoc:
54
- connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable, async: async)
69
+ def _query_by_sql(connection, sql, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
70
+ connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable, async: async, allow_retry: allow_retry)
55
71
  end
56
72
 
57
73
  def _load_from_sql(result_set, &block) # :nodoc:
@@ -91,7 +107,16 @@ module ActiveRecord
91
107
  #
92
108
  # * +sql+ - An SQL statement which should return a count query from the database, see the example above.
93
109
  def count_by_sql(sql)
94
- 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
113
+ end
114
+
115
+ # Same as #count_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
116
+ def async_count_by_sql(sql)
117
+ with_connection do |c|
118
+ c.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
119
+ end
95
120
  end
96
121
  end
97
122
  end