activerecord 6.1.6 → 7.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (309) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1627 -983
  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 +50 -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 +35 -31
  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.rb +26 -16
  27. data/lib/active_record/associations/preloader/association.rb +207 -52
  28. data/lib/active_record/associations/preloader/batch.rb +48 -0
  29. data/lib/active_record/associations/preloader/branch.rb +147 -0
  30. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  31. data/lib/active_record/associations/preloader.rb +50 -121
  32. data/lib/active_record/associations/singular_association.rb +9 -3
  33. data/lib/active_record/associations/through_association.rb +25 -14
  34. data/lib/active_record/associations.rb +439 -305
  35. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  36. data/lib/active_record/attribute_assignment.rb +1 -3
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  39. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  40. data/lib/active_record/attribute_methods/query.rb +31 -19
  41. data/lib/active_record/attribute_methods/read.rb +25 -10
  42. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  44. data/lib/active_record/attribute_methods/write.rb +10 -13
  45. data/lib/active_record/attribute_methods.rb +121 -40
  46. data/lib/active_record/attributes.rb +27 -38
  47. data/lib/active_record/autosave_association.rb +61 -30
  48. data/lib/active_record/base.rb +25 -2
  49. data/lib/active_record/callbacks.rb +18 -34
  50. data/lib/active_record/coders/column_serializer.rb +61 -0
  51. data/lib/active_record/coders/json.rb +1 -1
  52. data/lib/active_record/coders/yaml_column.rb +70 -34
  53. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  54. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -138
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -149
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
  69. data/lib/active_record/connection_adapters/column.rb +13 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  83. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  87. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  95. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  97. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +394 -74
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +509 -247
  101. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  102. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  103. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  104. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  107. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  108. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  109. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  110. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  111. data/lib/active_record/connection_adapters.rb +9 -6
  112. data/lib/active_record/connection_handling.rb +107 -136
  113. data/lib/active_record/core.rb +202 -223
  114. data/lib/active_record/counter_cache.rb +46 -25
  115. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  116. data/lib/active_record/database_configurations/database_config.rb +21 -12
  117. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  118. data/lib/active_record/database_configurations/url_config.rb +18 -12
  119. data/lib/active_record/database_configurations.rb +95 -59
  120. data/lib/active_record/delegated_type.rb +61 -15
  121. data/lib/active_record/deprecator.rb +7 -0
  122. data/lib/active_record/destroy_association_async_job.rb +3 -1
  123. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  124. data/lib/active_record/dynamic_matchers.rb +1 -1
  125. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  126. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  127. data/lib/active_record/encryption/cipher.rb +53 -0
  128. data/lib/active_record/encryption/config.rb +68 -0
  129. data/lib/active_record/encryption/configurable.rb +60 -0
  130. data/lib/active_record/encryption/context.rb +42 -0
  131. data/lib/active_record/encryption/contexts.rb +76 -0
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  134. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  136. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  137. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  138. data/lib/active_record/encryption/encryptor.rb +155 -0
  139. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  140. data/lib/active_record/encryption/errors.rb +15 -0
  141. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  142. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  143. data/lib/active_record/encryption/key.rb +28 -0
  144. data/lib/active_record/encryption/key_generator.rb +53 -0
  145. data/lib/active_record/encryption/key_provider.rb +46 -0
  146. data/lib/active_record/encryption/message.rb +33 -0
  147. data/lib/active_record/encryption/message_serializer.rb +92 -0
  148. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  149. data/lib/active_record/encryption/properties.rb +76 -0
  150. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  151. data/lib/active_record/encryption/scheme.rb +96 -0
  152. data/lib/active_record/encryption.rb +56 -0
  153. data/lib/active_record/enum.rb +154 -63
  154. data/lib/active_record/errors.rb +171 -15
  155. data/lib/active_record/explain.rb +23 -3
  156. data/lib/active_record/explain_registry.rb +11 -6
  157. data/lib/active_record/explain_subscriber.rb +1 -1
  158. data/lib/active_record/fixture_set/file.rb +15 -1
  159. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  160. data/lib/active_record/fixture_set/render_context.rb +2 -0
  161. data/lib/active_record/fixture_set/table_row.rb +70 -14
  162. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  163. data/lib/active_record/fixtures.rb +131 -86
  164. data/lib/active_record/future_result.rb +164 -0
  165. data/lib/active_record/gem_version.rb +3 -3
  166. data/lib/active_record/inheritance.rb +81 -29
  167. data/lib/active_record/insert_all.rb +135 -22
  168. data/lib/active_record/integration.rb +11 -10
  169. data/lib/active_record/internal_metadata.rb +119 -33
  170. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  171. data/lib/active_record/locking/optimistic.rb +36 -21
  172. data/lib/active_record/locking/pessimistic.rb +15 -6
  173. data/lib/active_record/log_subscriber.rb +52 -19
  174. data/lib/active_record/marshalling.rb +56 -0
  175. data/lib/active_record/message_pack.rb +124 -0
  176. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  177. data/lib/active_record/middleware/database_selector.rb +23 -13
  178. data/lib/active_record/middleware/shard_selector.rb +62 -0
  179. data/lib/active_record/migration/command_recorder.rb +112 -14
  180. data/lib/active_record/migration/compatibility.rb +221 -48
  181. data/lib/active_record/migration/default_strategy.rb +23 -0
  182. data/lib/active_record/migration/execution_strategy.rb +19 -0
  183. data/lib/active_record/migration/join_table.rb +1 -1
  184. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  185. data/lib/active_record/migration.rb +358 -171
  186. data/lib/active_record/model_schema.rb +120 -101
  187. data/lib/active_record/nested_attributes.rb +37 -18
  188. data/lib/active_record/no_touching.rb +3 -3
  189. data/lib/active_record/normalization.rb +167 -0
  190. data/lib/active_record/persistence.rb +405 -85
  191. data/lib/active_record/promise.rb +84 -0
  192. data/lib/active_record/query_cache.rb +3 -21
  193. data/lib/active_record/query_logs.rb +174 -0
  194. data/lib/active_record/query_logs_formatter.rb +41 -0
  195. data/lib/active_record/querying.rb +29 -6
  196. data/lib/active_record/railtie.rb +219 -43
  197. data/lib/active_record/railties/controller_runtime.rb +13 -9
  198. data/lib/active_record/railties/databases.rake +188 -252
  199. data/lib/active_record/railties/job_runtime.rb +23 -0
  200. data/lib/active_record/readonly_attributes.rb +41 -3
  201. data/lib/active_record/reflection.rb +241 -80
  202. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  203. data/lib/active_record/relation/batches.rb +192 -63
  204. data/lib/active_record/relation/calculations.rb +219 -90
  205. data/lib/active_record/relation/delegation.rb +27 -13
  206. data/lib/active_record/relation/finder_methods.rb +108 -51
  207. data/lib/active_record/relation/merger.rb +22 -13
  208. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  209. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  210. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  211. data/lib/active_record/relation/predicate_builder.rb +27 -20
  212. data/lib/active_record/relation/query_attribute.rb +30 -12
  213. data/lib/active_record/relation/query_methods.rb +654 -127
  214. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  215. data/lib/active_record/relation/spawn_methods.rb +20 -3
  216. data/lib/active_record/relation/where_clause.rb +10 -19
  217. data/lib/active_record/relation.rb +262 -120
  218. data/lib/active_record/result.rb +37 -11
  219. data/lib/active_record/runtime_registry.rb +18 -13
  220. data/lib/active_record/sanitization.rb +65 -20
  221. data/lib/active_record/schema.rb +36 -22
  222. data/lib/active_record/schema_dumper.rb +73 -24
  223. data/lib/active_record/schema_migration.rb +68 -33
  224. data/lib/active_record/scoping/default.rb +72 -15
  225. data/lib/active_record/scoping/named.rb +5 -13
  226. data/lib/active_record/scoping.rb +65 -34
  227. data/lib/active_record/secure_password.rb +60 -0
  228. data/lib/active_record/secure_token.rb +21 -3
  229. data/lib/active_record/serialization.rb +6 -1
  230. data/lib/active_record/signed_id.rb +10 -8
  231. data/lib/active_record/store.rb +16 -11
  232. data/lib/active_record/suppressor.rb +13 -15
  233. data/lib/active_record/table_metadata.rb +16 -3
  234. data/lib/active_record/tasks/database_tasks.rb +225 -136
  235. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  236. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  237. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  238. data/lib/active_record/test_databases.rb +1 -1
  239. data/lib/active_record/test_fixtures.rb +123 -99
  240. data/lib/active_record/timestamp.rb +29 -18
  241. data/lib/active_record/token_for.rb +113 -0
  242. data/lib/active_record/touch_later.rb +11 -6
  243. data/lib/active_record/transactions.rb +48 -27
  244. data/lib/active_record/translation.rb +3 -3
  245. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  246. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  247. data/lib/active_record/type/internal/timezone.rb +7 -2
  248. data/lib/active_record/type/serialized.rb +9 -5
  249. data/lib/active_record/type/time.rb +4 -0
  250. data/lib/active_record/type/type_map.rb +17 -20
  251. data/lib/active_record/type.rb +1 -2
  252. data/lib/active_record/validations/absence.rb +1 -1
  253. data/lib/active_record/validations/associated.rb +4 -4
  254. data/lib/active_record/validations/numericality.rb +5 -4
  255. data/lib/active_record/validations/presence.rb +5 -28
  256. data/lib/active_record/validations/uniqueness.rb +51 -6
  257. data/lib/active_record/validations.rb +8 -4
  258. data/lib/active_record/version.rb +1 -1
  259. data/lib/active_record.rb +335 -32
  260. data/lib/arel/attributes/attribute.rb +0 -8
  261. data/lib/arel/crud.rb +28 -22
  262. data/lib/arel/delete_manager.rb +18 -4
  263. data/lib/arel/errors.rb +10 -0
  264. data/lib/arel/factory_methods.rb +4 -0
  265. data/lib/arel/filter_predications.rb +9 -0
  266. data/lib/arel/insert_manager.rb +2 -3
  267. data/lib/arel/nodes/and.rb +4 -0
  268. data/lib/arel/nodes/binary.rb +6 -1
  269. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  270. data/lib/arel/nodes/casted.rb +1 -1
  271. data/lib/arel/nodes/cte.rb +36 -0
  272. data/lib/arel/nodes/delete_statement.rb +12 -13
  273. data/lib/arel/nodes/filter.rb +10 -0
  274. data/lib/arel/nodes/fragments.rb +35 -0
  275. data/lib/arel/nodes/function.rb +1 -0
  276. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  277. data/lib/arel/nodes/insert_statement.rb +2 -2
  278. data/lib/arel/nodes/leading_join.rb +8 -0
  279. data/lib/arel/nodes/node.rb +111 -2
  280. data/lib/arel/nodes/select_core.rb +2 -2
  281. data/lib/arel/nodes/select_statement.rb +2 -2
  282. data/lib/arel/nodes/sql_literal.rb +6 -0
  283. data/lib/arel/nodes/table_alias.rb +4 -0
  284. data/lib/arel/nodes/update_statement.rb +8 -3
  285. data/lib/arel/nodes.rb +5 -0
  286. data/lib/arel/predications.rb +13 -3
  287. data/lib/arel/select_manager.rb +10 -4
  288. data/lib/arel/table.rb +9 -6
  289. data/lib/arel/tree_manager.rb +0 -12
  290. data/lib/arel/update_manager.rb +18 -4
  291. data/lib/arel/visitors/dot.rb +80 -90
  292. data/lib/arel/visitors/mysql.rb +16 -3
  293. data/lib/arel/visitors/postgresql.rb +0 -10
  294. data/lib/arel/visitors/to_sql.rb +139 -19
  295. data/lib/arel/visitors/visitor.rb +2 -2
  296. data/lib/arel.rb +18 -3
  297. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  298. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  299. data/lib/rails/generators/active_record/migration.rb +3 -1
  300. data/lib/rails/generators/active_record/model/USAGE +113 -0
  301. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  302. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  303. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  304. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  305. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  306. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  307. metadata +93 -13
  308. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  309. 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