activerecord 6.1.7 → 7.1.5

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 (311) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2030 -1020
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +51 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +39 -35
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  27. data/lib/active_record/associations/join_dependency.rb +28 -20
  28. data/lib/active_record/associations/preloader/association.rb +210 -52
  29. data/lib/active_record/associations/preloader/batch.rb +48 -0
  30. data/lib/active_record/associations/preloader/branch.rb +147 -0
  31. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  32. data/lib/active_record/associations/preloader.rb +50 -121
  33. data/lib/active_record/associations/singular_association.rb +9 -3
  34. data/lib/active_record/associations/through_association.rb +25 -14
  35. data/lib/active_record/associations.rb +446 -306
  36. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  37. data/lib/active_record/attribute_assignment.rb +1 -3
  38. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  39. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  40. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  41. data/lib/active_record/attribute_methods/query.rb +31 -19
  42. data/lib/active_record/attribute_methods/read.rb +27 -12
  43. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  45. data/lib/active_record/attribute_methods/write.rb +12 -15
  46. data/lib/active_record/attribute_methods.rb +161 -40
  47. data/lib/active_record/attributes.rb +27 -38
  48. data/lib/active_record/autosave_association.rb +65 -31
  49. data/lib/active_record/base.rb +25 -2
  50. data/lib/active_record/callbacks.rb +18 -34
  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 -46
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +367 -141
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
  70. data/lib/active_record/connection_adapters/column.rb +13 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +39 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  89. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
  101. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
  103. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  104. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  105. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  106. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  107. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  108. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  109. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
  110. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  111. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  112. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  113. data/lib/active_record/connection_adapters.rb +9 -6
  114. data/lib/active_record/connection_handling.rb +108 -137
  115. data/lib/active_record/core.rb +242 -233
  116. data/lib/active_record/counter_cache.rb +52 -27
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
  118. data/lib/active_record/database_configurations/database_config.rb +21 -12
  119. data/lib/active_record/database_configurations/hash_config.rb +88 -16
  120. data/lib/active_record/database_configurations/url_config.rb +18 -12
  121. data/lib/active_record/database_configurations.rb +95 -59
  122. data/lib/active_record/delegated_type.rb +66 -20
  123. data/lib/active_record/deprecator.rb +7 -0
  124. data/lib/active_record/destroy_association_async_job.rb +4 -2
  125. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  126. data/lib/active_record/dynamic_matchers.rb +1 -1
  127. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  128. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  129. data/lib/active_record/encryption/cipher.rb +53 -0
  130. data/lib/active_record/encryption/config.rb +68 -0
  131. data/lib/active_record/encryption/configurable.rb +60 -0
  132. data/lib/active_record/encryption/context.rb +42 -0
  133. data/lib/active_record/encryption/contexts.rb +76 -0
  134. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  135. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  136. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  137. data/lib/active_record/encryption/encrypted_attribute_type.rb +155 -0
  138. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  139. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  140. data/lib/active_record/encryption/encryptor.rb +155 -0
  141. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  142. data/lib/active_record/encryption/errors.rb +15 -0
  143. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  144. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  145. data/lib/active_record/encryption/key.rb +28 -0
  146. data/lib/active_record/encryption/key_generator.rb +53 -0
  147. data/lib/active_record/encryption/key_provider.rb +46 -0
  148. data/lib/active_record/encryption/message.rb +33 -0
  149. data/lib/active_record/encryption/message_serializer.rb +92 -0
  150. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  151. data/lib/active_record/encryption/properties.rb +76 -0
  152. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  153. data/lib/active_record/encryption/scheme.rb +100 -0
  154. data/lib/active_record/encryption.rb +58 -0
  155. data/lib/active_record/enum.rb +154 -63
  156. data/lib/active_record/errors.rb +172 -15
  157. data/lib/active_record/explain.rb +23 -3
  158. data/lib/active_record/explain_registry.rb +11 -6
  159. data/lib/active_record/explain_subscriber.rb +1 -1
  160. data/lib/active_record/fixture_set/file.rb +15 -1
  161. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  162. data/lib/active_record/fixture_set/render_context.rb +2 -0
  163. data/lib/active_record/fixture_set/table_row.rb +70 -14
  164. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  165. data/lib/active_record/fixtures.rb +147 -86
  166. data/lib/active_record/future_result.rb +174 -0
  167. data/lib/active_record/gem_version.rb +3 -3
  168. data/lib/active_record/inheritance.rb +81 -29
  169. data/lib/active_record/insert_all.rb +135 -22
  170. data/lib/active_record/integration.rb +11 -10
  171. data/lib/active_record/internal_metadata.rb +119 -33
  172. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  173. data/lib/active_record/locking/optimistic.rb +37 -22
  174. data/lib/active_record/locking/pessimistic.rb +15 -6
  175. data/lib/active_record/log_subscriber.rb +52 -19
  176. data/lib/active_record/marshalling.rb +59 -0
  177. data/lib/active_record/message_pack.rb +124 -0
  178. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  179. data/lib/active_record/middleware/database_selector.rb +23 -13
  180. data/lib/active_record/middleware/shard_selector.rb +62 -0
  181. data/lib/active_record/migration/command_recorder.rb +112 -14
  182. data/lib/active_record/migration/compatibility.rb +233 -46
  183. data/lib/active_record/migration/default_strategy.rb +23 -0
  184. data/lib/active_record/migration/execution_strategy.rb +19 -0
  185. data/lib/active_record/migration/join_table.rb +1 -1
  186. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  187. data/lib/active_record/migration.rb +361 -173
  188. data/lib/active_record/model_schema.rb +125 -101
  189. data/lib/active_record/nested_attributes.rb +50 -20
  190. data/lib/active_record/no_touching.rb +3 -3
  191. data/lib/active_record/normalization.rb +167 -0
  192. data/lib/active_record/persistence.rb +409 -88
  193. data/lib/active_record/promise.rb +84 -0
  194. data/lib/active_record/query_cache.rb +4 -22
  195. data/lib/active_record/query_logs.rb +174 -0
  196. data/lib/active_record/query_logs_formatter.rb +41 -0
  197. data/lib/active_record/querying.rb +29 -6
  198. data/lib/active_record/railtie.rb +220 -44
  199. data/lib/active_record/railties/controller_runtime.rb +15 -10
  200. data/lib/active_record/railties/databases.rake +188 -252
  201. data/lib/active_record/railties/job_runtime.rb +23 -0
  202. data/lib/active_record/readonly_attributes.rb +41 -3
  203. data/lib/active_record/reflection.rb +248 -81
  204. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  205. data/lib/active_record/relation/batches.rb +192 -63
  206. data/lib/active_record/relation/calculations.rb +246 -90
  207. data/lib/active_record/relation/delegation.rb +28 -14
  208. data/lib/active_record/relation/finder_methods.rb +108 -51
  209. data/lib/active_record/relation/merger.rb +22 -13
  210. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  211. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  212. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  213. data/lib/active_record/relation/predicate_builder.rb +27 -20
  214. data/lib/active_record/relation/query_attribute.rb +30 -12
  215. data/lib/active_record/relation/query_methods.rb +670 -129
  216. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  217. data/lib/active_record/relation/spawn_methods.rb +20 -3
  218. data/lib/active_record/relation/where_clause.rb +10 -19
  219. data/lib/active_record/relation.rb +287 -120
  220. data/lib/active_record/result.rb +37 -11
  221. data/lib/active_record/runtime_registry.rb +32 -13
  222. data/lib/active_record/sanitization.rb +65 -20
  223. data/lib/active_record/schema.rb +36 -22
  224. data/lib/active_record/schema_dumper.rb +73 -24
  225. data/lib/active_record/schema_migration.rb +68 -33
  226. data/lib/active_record/scoping/default.rb +72 -15
  227. data/lib/active_record/scoping/named.rb +5 -13
  228. data/lib/active_record/scoping.rb +65 -34
  229. data/lib/active_record/secure_password.rb +60 -0
  230. data/lib/active_record/secure_token.rb +21 -3
  231. data/lib/active_record/serialization.rb +6 -1
  232. data/lib/active_record/signed_id.rb +10 -8
  233. data/lib/active_record/store.rb +10 -10
  234. data/lib/active_record/suppressor.rb +13 -15
  235. data/lib/active_record/table_metadata.rb +16 -3
  236. data/lib/active_record/tasks/database_tasks.rb +251 -140
  237. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  238. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  239. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  240. data/lib/active_record/test_databases.rb +1 -1
  241. data/lib/active_record/test_fixtures.rb +117 -96
  242. data/lib/active_record/timestamp.rb +32 -19
  243. data/lib/active_record/token_for.rb +113 -0
  244. data/lib/active_record/touch_later.rb +11 -6
  245. data/lib/active_record/transactions.rb +48 -27
  246. data/lib/active_record/translation.rb +3 -3
  247. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  248. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  249. data/lib/active_record/type/internal/timezone.rb +7 -2
  250. data/lib/active_record/type/serialized.rb +9 -5
  251. data/lib/active_record/type/time.rb +4 -0
  252. data/lib/active_record/type/type_map.rb +17 -20
  253. data/lib/active_record/type.rb +1 -2
  254. data/lib/active_record/validations/absence.rb +1 -1
  255. data/lib/active_record/validations/associated.rb +4 -4
  256. data/lib/active_record/validations/numericality.rb +5 -4
  257. data/lib/active_record/validations/presence.rb +5 -28
  258. data/lib/active_record/validations/uniqueness.rb +51 -6
  259. data/lib/active_record/validations.rb +8 -4
  260. data/lib/active_record/version.rb +1 -1
  261. data/lib/active_record.rb +335 -32
  262. data/lib/arel/attributes/attribute.rb +0 -8
  263. data/lib/arel/crud.rb +28 -22
  264. data/lib/arel/delete_manager.rb +18 -4
  265. data/lib/arel/errors.rb +10 -0
  266. data/lib/arel/factory_methods.rb +4 -0
  267. data/lib/arel/filter_predications.rb +9 -0
  268. data/lib/arel/insert_manager.rb +2 -3
  269. data/lib/arel/nodes/and.rb +4 -0
  270. data/lib/arel/nodes/binary.rb +6 -1
  271. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  272. data/lib/arel/nodes/casted.rb +1 -1
  273. data/lib/arel/nodes/cte.rb +36 -0
  274. data/lib/arel/nodes/delete_statement.rb +12 -13
  275. data/lib/arel/nodes/filter.rb +10 -0
  276. data/lib/arel/nodes/fragments.rb +35 -0
  277. data/lib/arel/nodes/function.rb +1 -0
  278. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  279. data/lib/arel/nodes/insert_statement.rb +2 -2
  280. data/lib/arel/nodes/leading_join.rb +8 -0
  281. data/lib/arel/nodes/node.rb +111 -2
  282. data/lib/arel/nodes/select_core.rb +2 -2
  283. data/lib/arel/nodes/select_statement.rb +2 -2
  284. data/lib/arel/nodes/sql_literal.rb +6 -0
  285. data/lib/arel/nodes/table_alias.rb +4 -0
  286. data/lib/arel/nodes/update_statement.rb +8 -3
  287. data/lib/arel/nodes.rb +5 -0
  288. data/lib/arel/predications.rb +13 -3
  289. data/lib/arel/select_manager.rb +10 -4
  290. data/lib/arel/table.rb +9 -6
  291. data/lib/arel/tree_manager.rb +5 -13
  292. data/lib/arel/update_manager.rb +18 -4
  293. data/lib/arel/visitors/dot.rb +80 -90
  294. data/lib/arel/visitors/mysql.rb +16 -3
  295. data/lib/arel/visitors/postgresql.rb +0 -10
  296. data/lib/arel/visitors/to_sql.rb +141 -20
  297. data/lib/arel/visitors/visitor.rb +2 -2
  298. data/lib/arel.rb +18 -3
  299. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  300. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/migration.rb +3 -1
  302. data/lib/rails/generators/active_record/model/USAGE +113 -0
  303. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  304. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  305. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  306. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  307. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  308. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  309. metadata +96 -16
  310. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  311. data/lib/active_record/null_relation.rb +0 -67
@@ -0,0 +1,367 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "concurrent/map"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ # = Active Record Connection Handler
9
+ #
10
+ # ConnectionHandler is a collection of ConnectionPool objects. It is used
11
+ # for keeping separate connection pools that connect to different databases.
12
+ #
13
+ # For example, suppose that you have 5 models, with the following hierarchy:
14
+ #
15
+ # class Author < ActiveRecord::Base
16
+ # end
17
+ #
18
+ # class BankAccount < ActiveRecord::Base
19
+ # end
20
+ #
21
+ # class Book < ActiveRecord::Base
22
+ # establish_connection :library_db
23
+ # end
24
+ #
25
+ # class ScaryBook < Book
26
+ # end
27
+ #
28
+ # class GoodBook < Book
29
+ # end
30
+ #
31
+ # And a database.yml that looked like this:
32
+ #
33
+ # development:
34
+ # database: my_application
35
+ # host: localhost
36
+ #
37
+ # library_db:
38
+ # database: library
39
+ # host: some.library.org
40
+ #
41
+ # Your primary database in the development environment is "my_application"
42
+ # but the Book model connects to a separate database called "library_db"
43
+ # (this can even be a database on a different machine).
44
+ #
45
+ # Book, ScaryBook, and GoodBook will all use the same connection pool to
46
+ # "library_db" while Author, BankAccount, and any other models you create
47
+ # will use the default connection pool to "my_application".
48
+ #
49
+ # The various connection pools are managed by a single instance of
50
+ # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
51
+ # All Active Record models use this handler to determine the connection pool that they
52
+ # should use.
53
+ #
54
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
55
+ # about the model. The model needs to pass a connection specification name to the handler,
56
+ # in order to look up the correct connection pool.
57
+ class ConnectionHandler
58
+ FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
59
+ private_constant :FINALIZER
60
+
61
+ class StringConnectionName # :nodoc:
62
+ attr_reader :name
63
+
64
+ def initialize(name)
65
+ @name = name
66
+ end
67
+
68
+ def primary_class?
69
+ false
70
+ end
71
+
72
+ def current_preventing_writes
73
+ false
74
+ end
75
+ end
76
+
77
+ def initialize
78
+ # These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
79
+ @connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
80
+
81
+ # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
82
+ ObjectSpace.define_finalizer self, FINALIZER
83
+ end
84
+
85
+ def prevent_writes # :nodoc:
86
+ ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes]
87
+ end
88
+
89
+ def prevent_writes=(prevent_writes) # :nodoc:
90
+ ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes
91
+ end
92
+
93
+ def connection_pool_names # :nodoc:
94
+ connection_name_to_pool_manager.keys
95
+ end
96
+
97
+ def all_connection_pools
98
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
99
+ The `all_connection_pools` method is deprecated in favor of `connection_pool_list`.
100
+ Call `connection_pool_list(:all)` to get the same behavior as `all_connection_pools`.
101
+ MSG
102
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
103
+ end
104
+
105
+ # Returns the pools for a connection handler and given role. If +:all+ is passed,
106
+ # all pools belonging to the connection handler will be returned.
107
+ def connection_pool_list(role = nil)
108
+ if role.nil?
109
+ deprecation_for_pool_handling(__method__)
110
+ role = ActiveRecord::Base.current_role
111
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
112
+ elsif role == :all
113
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
114
+ else
115
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
116
+ end
117
+ end
118
+ alias :connection_pools :connection_pool_list
119
+
120
+ def each_connection_pool(role = nil, &block) # :nodoc:
121
+ role = nil if role == :all
122
+ return enum_for(__method__, role) unless block_given?
123
+
124
+ connection_name_to_pool_manager.each_value do |manager|
125
+ manager.each_pool_config(role) do |pool_config|
126
+ yield pool_config.pool
127
+ end
128
+ end
129
+ end
130
+
131
+ def establish_connection(config, owner_name: Base, role: Base.current_role, shard: Base.current_shard, clobber: false)
132
+ owner_name = determine_owner_name(owner_name, config)
133
+
134
+ pool_config = resolve_pool_config(config, owner_name, role, shard)
135
+ db_config = pool_config.db_config
136
+
137
+ pool_manager = set_pool_manager(pool_config.connection_name)
138
+
139
+ # If there is an existing pool with the same values as the pool_config
140
+ # don't remove the connection. Connections should only be removed if we are
141
+ # establishing a connection on a class that is already connected to a different
142
+ # configuration.
143
+ existing_pool_config = pool_manager.get_pool_config(role, shard)
144
+
145
+ if !clobber && existing_pool_config && existing_pool_config.db_config == db_config
146
+ # Update the pool_config's connection class if it differs. This is used
147
+ # for ensuring that ActiveRecord::Base and the primary_abstract_class use
148
+ # the same pool. Without this granular swapping will not work correctly.
149
+ if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
150
+ existing_pool_config.connection_class = owner_name
151
+ end
152
+
153
+ existing_pool_config.pool
154
+ else
155
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
156
+ pool_manager.set_pool_config(role, shard, pool_config)
157
+
158
+ payload = {
159
+ connection_name: pool_config.connection_name,
160
+ role: role,
161
+ shard: shard,
162
+ config: db_config.configuration_hash
163
+ }
164
+
165
+ ActiveSupport::Notifications.instrumenter.instrument("!connection.active_record", payload) do
166
+ pool_config.pool
167
+ end
168
+ end
169
+ end
170
+
171
+ # Returns true if there are any active connections among the connection
172
+ # pools that the ConnectionHandler is managing.
173
+ def active_connections?(role = nil)
174
+ if role.nil?
175
+ deprecation_for_pool_handling(__method__)
176
+ role = ActiveRecord::Base.current_role
177
+ end
178
+
179
+ each_connection_pool(role).any?(&:active_connection?)
180
+ end
181
+
182
+ # Returns any connections in use by the current thread back to the pool,
183
+ # and also returns connections to the pool cached by threads that are no
184
+ # longer alive.
185
+ def clear_active_connections!(role = nil)
186
+ if role.nil?
187
+ deprecation_for_pool_handling(__method__)
188
+ role = ActiveRecord::Base.current_role
189
+ end
190
+
191
+ each_connection_pool(role).each(&:release_connection)
192
+ end
193
+
194
+ # Clears the cache which maps classes.
195
+ #
196
+ # See ConnectionPool#clear_reloadable_connections! for details.
197
+ def clear_reloadable_connections!(role = nil)
198
+ if role.nil?
199
+ deprecation_for_pool_handling(__method__)
200
+ role = ActiveRecord::Base.current_role
201
+ end
202
+
203
+ each_connection_pool(role).each(&:clear_reloadable_connections!)
204
+ end
205
+
206
+ def clear_all_connections!(role = nil)
207
+ if role.nil?
208
+ deprecation_for_pool_handling(__method__)
209
+ role = ActiveRecord::Base.current_role
210
+ end
211
+
212
+ each_connection_pool(role).each(&:disconnect!)
213
+ end
214
+
215
+ # Disconnects all currently idle connections.
216
+ #
217
+ # See ConnectionPool#flush! for details.
218
+ def flush_idle_connections!(role = nil)
219
+ if role.nil?
220
+ deprecation_for_pool_handling(__method__)
221
+ role = ActiveRecord::Base.current_role
222
+ end
223
+
224
+ each_connection_pool(role).each(&:flush!)
225
+ end
226
+
227
+ # Locate the connection of the nearest super class. This can be an
228
+ # active or defined connection: if it is the latter, it will be
229
+ # opened and set as the active connection for the class it was defined
230
+ # for (not necessarily the current class).
231
+ def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
232
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
233
+
234
+ unless pool
235
+ if shard != ActiveRecord::Base.default_shard
236
+ message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
237
+ elsif role != ActiveRecord::Base.default_role
238
+ message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
239
+ else
240
+ message = "No connection pool for '#{connection_name}' found."
241
+ end
242
+
243
+ raise ConnectionNotEstablished, message
244
+ end
245
+
246
+ pool.connection
247
+ end
248
+
249
+ # Returns true if a connection that's accessible to this class has
250
+ # already been opened.
251
+ def connected?(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
252
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
253
+ pool && pool.connected?
254
+ end
255
+
256
+ def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
257
+ if pool_manager = get_pool_manager(connection_name)
258
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
259
+ end
260
+ end
261
+
262
+ # Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
263
+ # This makes retrieving the connection pool O(1) once the process is warm.
264
+ # When a connection is established or removed, we invalidate the cache.
265
+ def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
266
+ pool_config = get_pool_manager(connection_name)&.get_pool_config(role, shard)
267
+ pool_config&.pool
268
+ end
269
+
270
+ private
271
+ attr_reader :connection_name_to_pool_manager
272
+
273
+ # Returns the pool manager for a connection name / identifier.
274
+ def get_pool_manager(connection_name)
275
+ connection_name_to_pool_manager[connection_name]
276
+ end
277
+
278
+ # Get the existing pool manager or initialize and assign a new one.
279
+ def set_pool_manager(connection_name)
280
+ connection_name_to_pool_manager[connection_name] ||= PoolManager.new
281
+ end
282
+
283
+ def pool_managers
284
+ connection_name_to_pool_manager.values
285
+ end
286
+
287
+ def deprecation_for_pool_handling(method)
288
+ roles = []
289
+ pool_managers.each do |pool_manager|
290
+ roles << pool_manager.role_names
291
+ end
292
+
293
+ if roles.flatten.uniq.count > 1
294
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
295
+ `#{method}` currently only applies to connection pools in the current
296
+ role (`#{ActiveRecord::Base.current_role}`). In Rails 7.2, this method
297
+ will apply to all known pools, regardless of role. To affect only those
298
+ connections belonging to a specific role, pass the role name as an
299
+ argument. To switch to the new behavior, pass `:all` as the role name.
300
+ MSG
301
+ end
302
+ end
303
+
304
+ def disconnect_pool_from_pool_manager(pool_manager, role, shard)
305
+ pool_config = pool_manager.remove_pool_config(role, shard)
306
+
307
+ if pool_config
308
+ pool_config.disconnect!
309
+ pool_config.db_config
310
+ end
311
+ end
312
+
313
+ # Returns an instance of PoolConfig for a given adapter.
314
+ # Accepts a hash one layer deep that contains all connection information.
315
+ #
316
+ # == Example
317
+ #
318
+ # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
319
+ # pool_config = Base.configurations.resolve_pool_config(:production)
320
+ # pool_config.db_config.configuration_hash
321
+ # # => { host: "localhost", database: "foo", adapter: "sqlite3" }
322
+ #
323
+ def resolve_pool_config(config, connection_name, role, shard)
324
+ db_config = Base.configurations.resolve(config)
325
+
326
+ raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
327
+
328
+ # Require the adapter itself and give useful feedback about
329
+ # 1. Missing adapter gems and
330
+ # 2. Adapter gems' missing dependencies.
331
+ path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
332
+ begin
333
+ require path_to_adapter
334
+ rescue LoadError => e
335
+ # We couldn't require the adapter itself. Raise an exception that
336
+ # points out config typos and missing gems.
337
+ if e.path == path_to_adapter
338
+ # We can assume that a non-builtin adapter was specified, so it's
339
+ # either misspelled or missing from Gemfile.
340
+ raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
341
+
342
+ # Bubbled up from the adapter require. Prefix the exception message
343
+ # with some guidance about how to address it and reraise.
344
+ else
345
+ raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
346
+ end
347
+ end
348
+
349
+ unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
350
+ raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
351
+ end
352
+
353
+ ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
354
+ end
355
+
356
+ def determine_owner_name(owner_name, config)
357
+ if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
358
+ StringConnectionName.new(owner_name.to_s)
359
+ elsif config.is_a?(Symbol)
360
+ StringConnectionName.new(config.to_s)
361
+ else
362
+ owner_name
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "monitor"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ class ConnectionPool
9
+ # = Active Record Connection Pool \Queue
10
+ #
11
+ # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
12
+ # with which it shares a Monitor.
13
+ class Queue
14
+ def initialize(lock = Monitor.new)
15
+ @lock = lock
16
+ @cond = @lock.new_cond
17
+ @num_waiting = 0
18
+ @queue = []
19
+ end
20
+
21
+ # Test if any threads are currently waiting on the queue.
22
+ def any_waiting?
23
+ synchronize do
24
+ @num_waiting > 0
25
+ end
26
+ end
27
+
28
+ # Returns the number of threads currently waiting on this
29
+ # queue.
30
+ def num_waiting
31
+ synchronize do
32
+ @num_waiting
33
+ end
34
+ end
35
+
36
+ # Add +element+ to the queue. Never blocks.
37
+ def add(element)
38
+ synchronize do
39
+ @queue.push element
40
+ @cond.signal
41
+ end
42
+ end
43
+
44
+ # If +element+ is in the queue, remove and return it, or +nil+.
45
+ def delete(element)
46
+ synchronize do
47
+ @queue.delete(element)
48
+ end
49
+ end
50
+
51
+ # Remove all elements from the queue.
52
+ def clear
53
+ synchronize do
54
+ @queue.clear
55
+ end
56
+ end
57
+
58
+ # Remove the head of the queue.
59
+ #
60
+ # If +timeout+ is not given, remove and return the head of the
61
+ # queue if the number of available elements is strictly
62
+ # greater than the number of threads currently waiting (that
63
+ # is, don't jump ahead in line). Otherwise, return +nil+.
64
+ #
65
+ # If +timeout+ is given, block if there is no element
66
+ # available, waiting up to +timeout+ seconds for an element to
67
+ # become available.
68
+ #
69
+ # Raises:
70
+ # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
71
+ # becomes available within +timeout+ seconds,
72
+ def poll(timeout = nil)
73
+ synchronize { internal_poll(timeout) }
74
+ end
75
+
76
+ private
77
+ def internal_poll(timeout)
78
+ no_wait_poll || (timeout && wait_poll(timeout))
79
+ end
80
+
81
+ def synchronize(&block)
82
+ @lock.synchronize(&block)
83
+ end
84
+
85
+ # Test if the queue currently contains any elements.
86
+ def any?
87
+ !@queue.empty?
88
+ end
89
+
90
+ # A thread can remove an element from the queue without
91
+ # waiting if and only if the number of currently available
92
+ # connections is strictly greater than the number of waiting
93
+ # threads.
94
+ def can_remove_no_wait?
95
+ @queue.size > @num_waiting
96
+ end
97
+
98
+ # Removes and returns the head of the queue if possible, or +nil+.
99
+ def remove
100
+ @queue.pop
101
+ end
102
+
103
+ # Remove and return the head of the queue if the number of
104
+ # available elements is strictly greater than the number of
105
+ # threads currently waiting. Otherwise, return +nil+.
106
+ def no_wait_poll
107
+ remove if can_remove_no_wait?
108
+ end
109
+
110
+ # Waits on the queue up to +timeout+ seconds, then removes and
111
+ # returns the head of the queue.
112
+ def wait_poll(timeout)
113
+ @num_waiting += 1
114
+
115
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
116
+ elapsed = 0
117
+ loop do
118
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
119
+ @cond.wait(timeout - elapsed)
120
+ end
121
+
122
+ return remove if any?
123
+
124
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
125
+ if elapsed >= timeout
126
+ msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
127
+ [timeout, elapsed]
128
+ raise ConnectionTimeoutError, msg
129
+ end
130
+ end
131
+ ensure
132
+ @num_waiting -= 1
133
+ end
134
+ end
135
+
136
+ # Adds the ability to turn a basic fair FIFO queue into one
137
+ # biased to some thread.
138
+ module BiasableQueue # :nodoc:
139
+ class BiasedConditionVariable # :nodoc:
140
+ # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
141
+ # +signal+ and +wait+ methods are only called while holding a lock
142
+ def initialize(lock, other_cond, preferred_thread)
143
+ @real_cond = lock.new_cond
144
+ @other_cond = other_cond
145
+ @preferred_thread = preferred_thread
146
+ @num_waiting_on_real_cond = 0
147
+ end
148
+
149
+ def broadcast
150
+ broadcast_on_biased
151
+ @other_cond.broadcast
152
+ end
153
+
154
+ def broadcast_on_biased
155
+ @num_waiting_on_real_cond = 0
156
+ @real_cond.broadcast
157
+ end
158
+
159
+ def signal
160
+ if @num_waiting_on_real_cond > 0
161
+ @num_waiting_on_real_cond -= 1
162
+ @real_cond
163
+ else
164
+ @other_cond
165
+ end.signal
166
+ end
167
+
168
+ def wait(timeout)
169
+ if Thread.current == @preferred_thread
170
+ @num_waiting_on_real_cond += 1
171
+ @real_cond
172
+ else
173
+ @other_cond
174
+ end.wait(timeout)
175
+ end
176
+ end
177
+
178
+ def with_a_bias_for(thread)
179
+ previous_cond = nil
180
+ new_cond = nil
181
+ synchronize do
182
+ previous_cond = @cond
183
+ @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
184
+ end
185
+ yield
186
+ ensure
187
+ synchronize do
188
+ @cond = previous_cond if previous_cond
189
+ new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
190
+ end
191
+ end
192
+ end
193
+
194
+ # Connections must be leased while holding the main pool mutex. This is
195
+ # an internal subclass that also +.leases+ returned connections while
196
+ # still in queue's critical section (queue synchronizes with the same
197
+ # <tt>@lock</tt> as the main pool) so that a returned connection is already
198
+ # leased and there is no need to re-enter synchronized block.
199
+ class ConnectionLeasingQueue < Queue # :nodoc:
200
+ include BiasableQueue
201
+
202
+ private
203
+ def internal_poll(timeout)
204
+ conn = super
205
+ conn.lease if conn
206
+ conn
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "weakref"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ class ConnectionPool
9
+ # = Active Record Connection Pool \Reaper
10
+ #
11
+ # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
12
+ # +pool+. A reaper instantiated with a zero frequency will never reap
13
+ # the connection pool.
14
+ #
15
+ # Configure the frequency by setting +reaping_frequency+ in your database
16
+ # YAML file (default 60 seconds).
17
+ class Reaper
18
+ attr_reader :pool, :frequency
19
+
20
+ def initialize(pool, frequency)
21
+ @pool = pool
22
+ @frequency = frequency
23
+ end
24
+
25
+ @mutex = Mutex.new
26
+ @pools = {}
27
+ @threads = {}
28
+
29
+ class << self
30
+ def register_pool(pool, frequency) # :nodoc:
31
+ @mutex.synchronize do
32
+ unless @threads[frequency]&.alive?
33
+ @threads[frequency] = spawn_thread(frequency)
34
+ end
35
+ @pools[frequency] ||= []
36
+ @pools[frequency] << WeakRef.new(pool)
37
+ end
38
+ end
39
+
40
+ private
41
+ def spawn_thread(frequency)
42
+ Thread.new(frequency) do |t|
43
+ # Advise multi-threaded app servers to ignore this thread for
44
+ # the purposes of fork safety warnings
45
+ Thread.current.thread_variable_set(:fork_safe, true)
46
+ running = true
47
+ while running
48
+ sleep t
49
+ @mutex.synchronize do
50
+ @pools[frequency].select! do |pool|
51
+ pool.weakref_alive? && !pool.discarded?
52
+ end
53
+
54
+ @pools[frequency].each do |p|
55
+ p.reap
56
+ p.flush
57
+ rescue WeakRef::RefError
58
+ end
59
+
60
+ if @pools[frequency].empty?
61
+ @pools.delete(frequency)
62
+ @threads.delete(frequency)
63
+ running = false
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def run
72
+ return unless frequency && frequency > 0
73
+ self.class.register_pool(pool, frequency)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end