activerecord 5.2.3 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (316) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +898 -532
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +5 -4
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +95 -42
  10. data/lib/active_record/associations/association_scope.rb +21 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +50 -46
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -5
  13. data/lib/active_record/associations/builder/association.rb +23 -21
  14. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -2
  18. data/lib/active_record/associations/builder/has_one.rb +33 -2
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  20. data/lib/active_record/associations/collection_association.rb +31 -29
  21. data/lib/active_record/associations/collection_proxy.rb +25 -21
  22. data/lib/active_record/associations/foreign_association.rb +20 -0
  23. data/lib/active_record/associations/has_many_association.rb +26 -13
  24. data/lib/active_record/associations/has_many_through_association.rb +27 -28
  25. data/lib/active_record/associations/has_one_association.rb +43 -31
  26. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  27. data/lib/active_record/associations/join_dependency/join_association.rb +54 -12
  28. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/preloader/association.rb +71 -43
  31. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  32. data/lib/active_record/associations/preloader.rb +48 -35
  33. data/lib/active_record/associations/singular_association.rb +3 -17
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/associations.rb +133 -25
  36. data/lib/active_record/attribute_assignment.rb +17 -19
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  38. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  40. data/lib/active_record/attribute_methods/query.rb +4 -8
  41. data/lib/active_record/attribute_methods/read.rb +14 -56
  42. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +18 -34
  45. data/lib/active_record/attribute_methods.rb +81 -143
  46. data/lib/active_record/attributes.rb +45 -8
  47. data/lib/active_record/autosave_association.rb +76 -47
  48. data/lib/active_record/base.rb +4 -17
  49. data/lib/active_record/callbacks.rb +158 -43
  50. data/lib/active_record/coders/yaml_column.rb +1 -2
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +293 -132
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +21 -17
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +203 -90
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +381 -146
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -98
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  64. data/lib/active_record/connection_adapters/column.rb +30 -12
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  77. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -54
  81. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  95. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  97. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  100. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +222 -112
  108. data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
  110. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  113. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  114. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +175 -187
  115. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  116. data/lib/active_record/connection_adapters.rb +50 -0
  117. data/lib/active_record/connection_handling.rb +285 -33
  118. data/lib/active_record/core.rb +308 -100
  119. data/lib/active_record/counter_cache.rb +8 -30
  120. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  121. data/lib/active_record/database_configurations/database_config.rb +80 -0
  122. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  123. data/lib/active_record/database_configurations/url_config.rb +53 -0
  124. data/lib/active_record/database_configurations.rb +272 -0
  125. data/lib/active_record/delegated_type.rb +209 -0
  126. data/lib/active_record/destroy_association_async_job.rb +36 -0
  127. data/lib/active_record/dynamic_matchers.rb +3 -4
  128. data/lib/active_record/enum.rb +71 -17
  129. data/lib/active_record/errors.rb +62 -19
  130. data/lib/active_record/explain.rb +10 -6
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +10 -17
  133. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  134. data/lib/active_record/fixture_set/render_context.rb +17 -0
  135. data/lib/active_record/fixture_set/table_row.rb +152 -0
  136. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  137. data/lib/active_record/fixtures.rb +197 -481
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +53 -24
  140. data/lib/active_record/insert_all.rb +208 -0
  141. data/lib/active_record/integration.rb +67 -17
  142. data/lib/active_record/internal_metadata.rb +26 -9
  143. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  144. data/lib/active_record/locking/optimistic.rb +26 -22
  145. data/lib/active_record/locking/pessimistic.rb +9 -5
  146. data/lib/active_record/log_subscriber.rb +34 -35
  147. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  149. data/lib/active_record/middleware/database_selector.rb +77 -0
  150. data/lib/active_record/migration/command_recorder.rb +96 -44
  151. data/lib/active_record/migration/compatibility.rb +141 -64
  152. data/lib/active_record/migration/join_table.rb +0 -1
  153. data/lib/active_record/migration.rb +205 -156
  154. data/lib/active_record/model_schema.rb +148 -22
  155. data/lib/active_record/nested_attributes.rb +4 -7
  156. data/lib/active_record/no_touching.rb +8 -1
  157. data/lib/active_record/null_relation.rb +0 -1
  158. data/lib/active_record/persistence.rb +267 -59
  159. data/lib/active_record/query_cache.rb +21 -4
  160. data/lib/active_record/querying.rb +40 -23
  161. data/lib/active_record/railtie.rb +115 -58
  162. data/lib/active_record/railties/controller_runtime.rb +30 -35
  163. data/lib/active_record/railties/databases.rake +402 -78
  164. data/lib/active_record/readonly_attributes.rb +4 -0
  165. data/lib/active_record/reflection.rb +113 -101
  166. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  167. data/lib/active_record/relation/batches.rb +44 -35
  168. data/lib/active_record/relation/calculations.rb +157 -93
  169. data/lib/active_record/relation/delegation.rb +35 -50
  170. data/lib/active_record/relation/finder_methods.rb +65 -40
  171. data/lib/active_record/relation/from_clause.rb +5 -1
  172. data/lib/active_record/relation/merger.rb +32 -40
  173. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  174. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -7
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  179. data/lib/active_record/relation/predicate_builder.rb +58 -40
  180. data/lib/active_record/relation/query_attribute.rb +13 -8
  181. data/lib/active_record/relation/query_methods.rb +487 -199
  182. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  183. data/lib/active_record/relation/spawn_methods.rb +9 -9
  184. data/lib/active_record/relation/where_clause.rb +108 -58
  185. data/lib/active_record/relation.rb +375 -104
  186. data/lib/active_record/result.rb +64 -38
  187. data/lib/active_record/runtime_registry.rb +2 -2
  188. data/lib/active_record/sanitization.rb +22 -41
  189. data/lib/active_record/schema.rb +2 -11
  190. data/lib/active_record/schema_dumper.rb +54 -9
  191. data/lib/active_record/schema_migration.rb +7 -9
  192. data/lib/active_record/scoping/default.rb +6 -8
  193. data/lib/active_record/scoping/named.rb +17 -24
  194. data/lib/active_record/scoping.rb +8 -9
  195. data/lib/active_record/secure_token.rb +16 -8
  196. data/lib/active_record/serialization.rb +5 -3
  197. data/lib/active_record/signed_id.rb +116 -0
  198. data/lib/active_record/statement_cache.rb +51 -8
  199. data/lib/active_record/store.rb +88 -9
  200. data/lib/active_record/suppressor.rb +2 -2
  201. data/lib/active_record/table_metadata.rb +39 -43
  202. data/lib/active_record/tasks/database_tasks.rb +276 -81
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  206. data/lib/active_record/test_databases.rb +24 -0
  207. data/lib/active_record/test_fixtures.rb +246 -0
  208. data/lib/active_record/timestamp.rb +43 -32
  209. data/lib/active_record/touch_later.rb +23 -22
  210. data/lib/active_record/transactions.rb +59 -117
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  213. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  214. data/lib/active_record/type/serialized.rb +6 -3
  215. data/lib/active_record/type/time.rb +10 -0
  216. data/lib/active_record/type/type_map.rb +0 -1
  217. data/lib/active_record/type/unsigned_integer.rb +0 -1
  218. data/lib/active_record/type.rb +10 -5
  219. data/lib/active_record/type_caster/connection.rb +15 -15
  220. data/lib/active_record/type_caster/map.rb +8 -8
  221. data/lib/active_record/validations/associated.rb +1 -2
  222. data/lib/active_record/validations/numericality.rb +35 -0
  223. data/lib/active_record/validations/uniqueness.rb +38 -30
  224. data/lib/active_record/validations.rb +4 -3
  225. data/lib/active_record.rb +13 -12
  226. data/lib/arel/alias_predication.rb +9 -0
  227. data/lib/arel/attributes/attribute.rb +41 -0
  228. data/lib/arel/collectors/bind.rb +29 -0
  229. data/lib/arel/collectors/composite.rb +39 -0
  230. data/lib/arel/collectors/plain_string.rb +20 -0
  231. data/lib/arel/collectors/sql_string.rb +27 -0
  232. data/lib/arel/collectors/substitute_binds.rb +35 -0
  233. data/lib/arel/crud.rb +42 -0
  234. data/lib/arel/delete_manager.rb +18 -0
  235. data/lib/arel/errors.rb +9 -0
  236. data/lib/arel/expressions.rb +29 -0
  237. data/lib/arel/factory_methods.rb +49 -0
  238. data/lib/arel/insert_manager.rb +49 -0
  239. data/lib/arel/math.rb +45 -0
  240. data/lib/arel/nodes/and.rb +32 -0
  241. data/lib/arel/nodes/ascending.rb +23 -0
  242. data/lib/arel/nodes/binary.rb +126 -0
  243. data/lib/arel/nodes/bind_param.rb +44 -0
  244. data/lib/arel/nodes/case.rb +55 -0
  245. data/lib/arel/nodes/casted.rb +62 -0
  246. data/lib/arel/nodes/comment.rb +29 -0
  247. data/lib/arel/nodes/count.rb +12 -0
  248. data/lib/arel/nodes/delete_statement.rb +45 -0
  249. data/lib/arel/nodes/descending.rb +23 -0
  250. data/lib/arel/nodes/equality.rb +15 -0
  251. data/lib/arel/nodes/extract.rb +24 -0
  252. data/lib/arel/nodes/false.rb +16 -0
  253. data/lib/arel/nodes/full_outer_join.rb +8 -0
  254. data/lib/arel/nodes/function.rb +44 -0
  255. data/lib/arel/nodes/grouping.rb +11 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  257. data/lib/arel/nodes/in.rb +15 -0
  258. data/lib/arel/nodes/infix_operation.rb +92 -0
  259. data/lib/arel/nodes/inner_join.rb +8 -0
  260. data/lib/arel/nodes/insert_statement.rb +37 -0
  261. data/lib/arel/nodes/join_source.rb +20 -0
  262. data/lib/arel/nodes/matches.rb +18 -0
  263. data/lib/arel/nodes/named_function.rb +23 -0
  264. data/lib/arel/nodes/node.rb +51 -0
  265. data/lib/arel/nodes/node_expression.rb +13 -0
  266. data/lib/arel/nodes/ordering.rb +27 -0
  267. data/lib/arel/nodes/outer_join.rb +8 -0
  268. data/lib/arel/nodes/over.rb +15 -0
  269. data/lib/arel/nodes/regexp.rb +16 -0
  270. data/lib/arel/nodes/right_outer_join.rb +8 -0
  271. data/lib/arel/nodes/select_core.rb +67 -0
  272. data/lib/arel/nodes/select_statement.rb +41 -0
  273. data/lib/arel/nodes/sql_literal.rb +19 -0
  274. data/lib/arel/nodes/string_join.rb +11 -0
  275. data/lib/arel/nodes/table_alias.rb +31 -0
  276. data/lib/arel/nodes/terminal.rb +16 -0
  277. data/lib/arel/nodes/true.rb +16 -0
  278. data/lib/arel/nodes/unary.rb +44 -0
  279. data/lib/arel/nodes/unary_operation.rb +20 -0
  280. data/lib/arel/nodes/unqualified_column.rb +22 -0
  281. data/lib/arel/nodes/update_statement.rb +41 -0
  282. data/lib/arel/nodes/values_list.rb +9 -0
  283. data/lib/arel/nodes/window.rb +126 -0
  284. data/lib/arel/nodes/with.rb +11 -0
  285. data/lib/arel/nodes.rb +70 -0
  286. data/lib/arel/order_predications.rb +13 -0
  287. data/lib/arel/predications.rb +250 -0
  288. data/lib/arel/select_manager.rb +270 -0
  289. data/lib/arel/table.rb +118 -0
  290. data/lib/arel/tree_manager.rb +72 -0
  291. data/lib/arel/update_manager.rb +34 -0
  292. data/lib/arel/visitors/dot.rb +308 -0
  293. data/lib/arel/visitors/mysql.rb +93 -0
  294. data/lib/arel/visitors/postgresql.rb +120 -0
  295. data/lib/arel/visitors/sqlite.rb +38 -0
  296. data/lib/arel/visitors/to_sql.rb +899 -0
  297. data/lib/arel/visitors/visitor.rb +45 -0
  298. data/lib/arel/visitors.rb +13 -0
  299. data/lib/arel/window_predications.rb +9 -0
  300. data/lib/arel.rb +54 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  303. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  304. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  305. data/lib/rails/generators/active_record/migration.rb +19 -2
  306. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  307. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  308. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  309. metadata +117 -32
  310. data/lib/active_record/attribute_decorators.rb +0 -90
  311. data/lib/active_record/collection_cache_key.rb +0 -53
  312. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  313. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  314. data/lib/active_record/define_callbacks.rb +0 -22
  315. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  316. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -3,22 +3,32 @@
3
3
  require "thread"
4
4
  require "concurrent/map"
5
5
  require "monitor"
6
+ require "weakref"
6
7
 
7
8
  module ActiveRecord
8
- # Raised when a connection could not be obtained within the connection
9
- # acquisition timeout period: because max connections in pool
10
- # are in use.
11
- class ConnectionTimeoutError < ConnectionNotEstablished
12
- end
9
+ module ConnectionAdapters
10
+ module AbstractPool # :nodoc:
11
+ def get_schema_cache(connection)
12
+ self.schema_cache ||= SchemaCache.new(connection)
13
+ schema_cache.connection = connection
14
+ schema_cache
15
+ end
13
16
 
14
- # Raised when a pool was unable to get ahold of all its connections
15
- # to perform a "group" action such as
16
- # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
17
- # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
18
- class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
19
- end
17
+ def set_schema_cache(cache)
18
+ self.schema_cache = cache
19
+ end
20
+ end
21
+
22
+ class NullPool # :nodoc:
23
+ include ConnectionAdapters::AbstractPool
24
+
25
+ attr_accessor :schema_cache
26
+
27
+ def owner_name
28
+ nil
29
+ end
30
+ end
20
31
 
21
- module ConnectionAdapters
22
32
  # Connection pool base class for managing Active Record database
23
33
  # connections.
24
34
  #
@@ -129,7 +139,7 @@ module ActiveRecord
129
139
 
130
140
  # Remove the head of the queue.
131
141
  #
132
- # If +timeout+ is not given, remove and return the head the
142
+ # If +timeout+ is not given, remove and return the head of the
133
143
  # queue if the number of available elements is strictly
134
144
  # greater than the number of threads currently waiting (that
135
145
  # is, don't jump ahead in line). Otherwise, return +nil+.
@@ -146,7 +156,6 @@ module ActiveRecord
146
156
  end
147
157
 
148
158
  private
149
-
150
159
  def internal_poll(timeout)
151
160
  no_wait_poll || (timeout && wait_poll(timeout))
152
161
  end
@@ -173,7 +182,7 @@ module ActiveRecord
173
182
  @queue.pop
174
183
  end
175
184
 
176
- # Remove and return the head the queue if the number of
185
+ # Remove and return the head of the queue if the number of
177
186
  # available elements is strictly greater than the number of
178
187
  # threads currently waiting. Otherwise, return +nil+.
179
188
  def no_wait_poll
@@ -185,7 +194,7 @@ module ActiveRecord
185
194
  def wait_poll(timeout)
186
195
  @num_waiting += 1
187
196
 
188
- t0 = Time.now
197
+ t0 = Concurrent.monotonic_time
189
198
  elapsed = 0
190
199
  loop do
191
200
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -194,7 +203,7 @@ module ActiveRecord
194
203
 
195
204
  return remove if any?
196
205
 
197
- elapsed = Time.now - t0
206
+ elapsed = Concurrent.monotonic_time - t0
198
207
  if elapsed >= timeout
199
208
  msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
200
209
  [timeout, elapsed]
@@ -294,50 +303,90 @@ module ActiveRecord
294
303
  @frequency = frequency
295
304
  end
296
305
 
306
+ @mutex = Mutex.new
307
+ @pools = {}
308
+ @threads = {}
309
+
310
+ class << self
311
+ def register_pool(pool, frequency) # :nodoc:
312
+ @mutex.synchronize do
313
+ unless @threads[frequency]&.alive?
314
+ @threads[frequency] = spawn_thread(frequency)
315
+ end
316
+ @pools[frequency] ||= []
317
+ @pools[frequency] << WeakRef.new(pool)
318
+ end
319
+ end
320
+
321
+ private
322
+ def spawn_thread(frequency)
323
+ Thread.new(frequency) do |t|
324
+ # Advise multi-threaded app servers to ignore this thread for
325
+ # the purposes of fork safety warnings
326
+ Thread.current.thread_variable_set(:fork_safe, true)
327
+ running = true
328
+ while running
329
+ sleep t
330
+ @mutex.synchronize do
331
+ @pools[frequency].select! do |pool|
332
+ pool.weakref_alive? && !pool.discarded?
333
+ end
334
+
335
+ @pools[frequency].each do |p|
336
+ p.reap
337
+ p.flush
338
+ rescue WeakRef::RefError
339
+ end
340
+
341
+ if @pools[frequency].empty?
342
+ @pools.delete(frequency)
343
+ @threads.delete(frequency)
344
+ running = false
345
+ end
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end
351
+
297
352
  def run
298
353
  return unless frequency && frequency > 0
299
- Thread.new(frequency, pool) { |t, p|
300
- loop do
301
- sleep t
302
- p.reap
303
- p.flush
304
- end
305
- }
354
+ self.class.register_pool(pool, frequency)
306
355
  end
307
356
  end
308
357
 
309
358
  include MonitorMixin
310
359
  include QueryCache::ConnectionPoolConfiguration
360
+ include ConnectionAdapters::AbstractPool
311
361
 
312
- attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
313
- attr_reader :spec, :connections, :size, :reaper
362
+ attr_accessor :automatic_reconnect, :checkout_timeout
363
+ attr_reader :db_config, :size, :reaper, :pool_config, :owner_name
314
364
 
315
- # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
365
+ delegate :schema_cache, :schema_cache=, to: :pool_config
366
+
367
+ # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
316
368
  # object which describes database connection information (e.g. adapter,
317
369
  # host name, username, password, etc), as well as the maximum size for
318
370
  # this ConnectionPool.
319
371
  #
320
372
  # The default ConnectionPool maximum size is 5.
321
- def initialize(spec)
373
+ def initialize(pool_config)
322
374
  super()
323
375
 
324
- @spec = spec
325
-
326
- @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
327
- if @idle_timeout = spec.config.fetch(:idle_timeout, 300)
328
- @idle_timeout = @idle_timeout.to_f
329
- @idle_timeout = nil if @idle_timeout <= 0
330
- end
376
+ @pool_config = pool_config
377
+ @db_config = pool_config.db_config
378
+ @owner_name = pool_config.connection_specification_name
331
379
 
332
- # default max pool size to 5
333
- @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
380
+ @checkout_timeout = db_config.checkout_timeout
381
+ @idle_timeout = db_config.idle_timeout
382
+ @size = db_config.pool
334
383
 
335
384
  # This variable tracks the cache of threads mapped to reserved connections, with the
336
385
  # sole purpose of speeding up the +connection+ method. It is not the authoritative
337
386
  # registry of which thread owns which connection. Connection ownership is tracked by
338
387
  # the +connection.owner+ attr on each +connection+ instance.
339
388
  # The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
340
- # then that +thread+ does indeed own that +conn+. However, an absence of a such
389
+ # then that +thread+ does indeed own that +conn+. However, an absence of such
341
390
  # mapping does not mean that the +thread+ doesn't own the said connection. In
342
391
  # that case +conn.owner+ attr should be consulted.
343
392
  # Access and modification of <tt>@thread_cached_conns</tt> does not require
@@ -358,10 +407,7 @@ module ActiveRecord
358
407
 
359
408
  @lock_thread = false
360
409
 
361
- # +reaping_frequency+ is configurable mostly for historical reasons, but it could
362
- # also be useful if someone wants a very low +idle_timeout+.
363
- reaping_frequency = spec.config.fetch(:reaping_frequency, 60)
364
- @reaper = Reaper.new(self, reaping_frequency && reaping_frequency.to_f)
410
+ @reaper = Reaper.new(self, db_config.reaping_frequency)
365
411
  @reaper.run
366
412
  end
367
413
 
@@ -379,7 +425,7 @@ module ActiveRecord
379
425
  # #connection can be called any number of times; the connection is
380
426
  # held in a cache keyed by a thread.
381
427
  def connection
382
- @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
428
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
383
429
  end
384
430
 
385
431
  # Returns true if there is an open connection being used for the current thread.
@@ -388,7 +434,7 @@ module ActiveRecord
388
434
  # #connection or #with_connection methods. Connections obtained through
389
435
  # #checkout will not be detected by #active_connection?
390
436
  def active_connection?
391
- @thread_cached_conns[connection_cache_key(Thread.current)]
437
+ @thread_cached_conns[connection_cache_key(current_thread)]
392
438
  end
393
439
 
394
440
  # Signal that the thread is finished with the current connection.
@@ -423,12 +469,27 @@ module ActiveRecord
423
469
  synchronize { @connections.any? }
424
470
  end
425
471
 
472
+ # Returns an array containing the connections currently in the pool.
473
+ # Access to the array does not require synchronization on the pool because
474
+ # the array is newly created and not retained by the pool.
475
+ #
476
+ # However; this method bypasses the ConnectionPool's thread-safe connection
477
+ # access pattern. A returned connection may be owned by another thread,
478
+ # unowned, or by happen-stance owned by the calling thread.
479
+ #
480
+ # Calling methods on a connection without ownership is subject to the
481
+ # thread-safety guarantees of the underlying method. Many of the methods
482
+ # on connection adapter classes are inherently multi-thread unsafe.
483
+ def connections
484
+ synchronize { @connections.dup }
485
+ end
486
+
426
487
  # Disconnects all connections in the pool, and clears the pool.
427
488
  #
428
489
  # Raises:
429
490
  # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
430
491
  # connections in the pool within a timeout interval (default duration is
431
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
492
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
432
493
  def disconnect(raise_on_acquisition_timeout = true)
433
494
  with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
434
495
  synchronize do
@@ -449,7 +510,7 @@ module ActiveRecord
449
510
  #
450
511
  # The pool first tries to gain ownership of all connections. If unable to
451
512
  # do so within a timeout interval (default duration is
452
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully
513
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool is forcefully
453
514
  # disconnected without any regard for other connection owning threads.
454
515
  def disconnect!
455
516
  disconnect(false)
@@ -462,7 +523,7 @@ module ActiveRecord
462
523
  # See AbstractAdapter#discard!
463
524
  def discard! # :nodoc:
464
525
  synchronize do
465
- return if @connections.nil? # already discarded
526
+ return if self.discarded?
466
527
  @connections.each do |conn|
467
528
  conn.discard!
468
529
  end
@@ -470,13 +531,17 @@ module ActiveRecord
470
531
  end
471
532
  end
472
533
 
534
+ def discarded? # :nodoc:
535
+ @connections.nil?
536
+ end
537
+
473
538
  # Clears the cache which maps classes and re-connects connections that
474
539
  # require reloading.
475
540
  #
476
541
  # Raises:
477
542
  # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
478
543
  # connections in the pool within a timeout interval (default duration is
479
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
544
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
480
545
  def clear_reloadable_connections(raise_on_acquisition_timeout = true)
481
546
  with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
482
547
  synchronize do
@@ -498,7 +563,7 @@ module ActiveRecord
498
563
  #
499
564
  # The pool first tries to gain ownership of all connections. If unable to
500
565
  # do so within a timeout interval (default duration is
501
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool forcefully
566
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool forcefully
502
567
  # clears the cache and reloads connections without any regard for other
503
568
  # connection owning threads.
504
569
  def clear_reloadable_connections!
@@ -578,6 +643,7 @@ module ActiveRecord
578
643
  # or a thread dies unexpectedly.
579
644
  def reap
580
645
  stale_connections = synchronize do
646
+ return if self.discarded?
581
647
  @connections.select do |conn|
582
648
  conn.in_use? && !conn.owner.alive?
583
649
  end.each do |conn|
@@ -602,6 +668,7 @@ module ActiveRecord
602
668
  return if minimum_idle.nil?
603
669
 
604
670
  idle_connections = synchronize do
671
+ return if self.discarded?
605
672
  @connections.select do |conn|
606
673
  !conn.in_use? && conn.seconds_idle >= minimum_idle
607
674
  end.each do |conn|
@@ -668,6 +735,10 @@ module ActiveRecord
668
735
  thread
669
736
  end
670
737
 
738
+ def current_thread
739
+ @lock_thread || Thread.current
740
+ end
741
+
671
742
  # Take control of all existing connections so a "group" action such as
672
743
  # reload/disconnect can be performed safely. It is no longer enough to
673
744
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -686,13 +757,13 @@ module ActiveRecord
686
757
  end
687
758
 
688
759
  newly_checked_out = []
689
- timeout_time = Time.now + (@checkout_timeout * 2)
760
+ timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
690
761
 
691
762
  @available.with_a_bias_for(Thread.current) do
692
763
  loop do
693
764
  synchronize do
694
765
  return if collected_conns.size == @connections.size && @now_connecting == 0
695
- remaining_timeout = timeout_time - Time.now
766
+ remaining_timeout = timeout_time - Concurrent.monotonic_time
696
767
  remaining_timeout = 0 if remaining_timeout < 0
697
768
  conn = checkout_for_exclusive_access(remaining_timeout)
698
769
  collected_conns << conn
@@ -731,7 +802,7 @@ module ActiveRecord
731
802
  # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
732
803
  # rescue block, because doing so would put it outside of synchronize section, without
733
804
  # being in a critical section thread_report might become inaccurate
734
- msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds".dup
805
+ msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds"
735
806
 
736
807
  thread_report = []
737
808
  @connections.each do |conn|
@@ -808,8 +879,8 @@ module ActiveRecord
808
879
  alias_method :release, :remove_connection_from_thread_cache
809
880
 
810
881
  def new_connection
811
- Base.send(spec.adapter_method, spec.config).tap do |conn|
812
- conn.schema_cache = schema_cache.dup if schema_cache
882
+ Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
883
+ conn.check_version
813
884
  end
814
885
  end
815
886
 
@@ -912,156 +983,246 @@ module ActiveRecord
912
983
  # should use.
913
984
  #
914
985
  # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
915
- # about the model. The model needs to pass a specification name to the handler,
986
+ # about the model. The model needs to pass a connection specification name to the handler,
916
987
  # in order to look up the correct connection pool.
917
988
  class ConnectionHandler
918
- def self.create_owner_to_pool # :nodoc:
919
- Concurrent::Map.new(initial_capacity: 2) do |h, k|
920
- # Discard the parent's connection pools immediately; we have no need
921
- # of them
922
- discard_unowned_pools(h)
989
+ FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
990
+ private_constant :FINALIZER
923
991
 
924
- h[k] = Concurrent::Map.new(initial_capacity: 2)
925
- end
992
+ def initialize
993
+ # These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name).
994
+ @owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
995
+
996
+ # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
997
+ ObjectSpace.define_finalizer self, FINALIZER
926
998
  end
927
999
 
928
- def self.unowned_pool_finalizer(pid_map) # :nodoc:
929
- lambda do |_|
930
- discard_unowned_pools(pid_map)
931
- end
1000
+ def prevent_writes # :nodoc:
1001
+ Thread.current[:prevent_writes]
932
1002
  end
933
1003
 
934
- def self.discard_unowned_pools(pid_map) # :nodoc:
935
- pid_map.each do |pid, pools|
936
- pools.values.compact.each(&:discard!) unless pid == Process.pid
1004
+ def prevent_writes=(prevent_writes) # :nodoc:
1005
+ Thread.current[:prevent_writes] = prevent_writes
1006
+ end
1007
+
1008
+ # Prevent writing to the database regardless of role.
1009
+ #
1010
+ # In some cases you may want to prevent writes to the database
1011
+ # even if you are on a database that can write. `while_preventing_writes`
1012
+ # will prevent writes to the database for the duration of the block.
1013
+ #
1014
+ # This method does not provide the same protection as a readonly
1015
+ # user and is meant to be a safeguard against accidental writes.
1016
+ #
1017
+ # See `READ_QUERY` for the queries that are blocked by this
1018
+ # method.
1019
+ def while_preventing_writes(enabled = true)
1020
+ unless ActiveRecord::Base.legacy_connection_handling
1021
+ raise NotImplementedError, "`while_preventing_writes` is only available on the connection_handler with legacy_connection_handling"
937
1022
  end
1023
+
1024
+ original, self.prevent_writes = self.prevent_writes, enabled
1025
+ yield
1026
+ ensure
1027
+ self.prevent_writes = original
938
1028
  end
939
1029
 
940
- def initialize
941
- # These caches are keyed by spec.name (ConnectionSpecification#name).
942
- @owner_to_pool = ConnectionHandler.create_owner_to_pool
1030
+ def connection_pool_names # :nodoc:
1031
+ owner_to_pool_manager.keys
1032
+ end
943
1033
 
944
- # Backup finalizer: if the forked child never needed a pool, the above
945
- # early discard has not occurred
946
- ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
1034
+ def all_connection_pools
1035
+ owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
947
1036
  end
948
1037
 
949
- def connection_pool_list
950
- owner_to_pool.values.compact
1038
+ def connection_pool_list(role = ActiveRecord::Base.current_role)
1039
+ owner_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
951
1040
  end
952
1041
  alias :connection_pools :connection_pool_list
953
1042
 
954
- def establish_connection(config)
955
- resolver = ConnectionSpecification::Resolver.new(Base.configurations)
956
- spec = resolver.spec(config)
1043
+ def establish_connection(config, owner_name: Base.name, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
1044
+ owner_name = config.to_s if config.is_a?(Symbol)
957
1045
 
958
- remove_connection(spec.name)
1046
+ pool_config = resolve_pool_config(config, owner_name)
1047
+ db_config = pool_config.db_config
1048
+
1049
+ # Protects the connection named `ActiveRecord::Base` from being removed
1050
+ # if the user calls `establish_connection :primary`.
1051
+ if owner_to_pool_manager.key?(pool_config.connection_specification_name)
1052
+ remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard)
1053
+ end
959
1054
 
960
1055
  message_bus = ActiveSupport::Notifications.instrumenter
961
- payload = {
962
- connection_id: object_id
963
- }
964
- if spec
965
- payload[:spec_name] = spec.name
966
- payload[:config] = spec.config
1056
+ payload = {}
1057
+ if pool_config
1058
+ payload[:spec_name] = pool_config.connection_specification_name
1059
+ payload[:shard] = shard
1060
+ payload[:config] = db_config.configuration_hash
967
1061
  end
968
1062
 
969
- message_bus.instrument("!connection.active_record", payload) do
970
- owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
1063
+ if ActiveRecord::Base.legacy_connection_handling
1064
+ owner_to_pool_manager[pool_config.connection_specification_name] ||= LegacyPoolManager.new
1065
+ else
1066
+ owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
971
1067
  end
1068
+ pool_manager = get_pool_manager(pool_config.connection_specification_name)
1069
+ pool_manager.set_pool_config(role, shard, pool_config)
972
1070
 
973
- owner_to_pool[spec.name]
1071
+ message_bus.instrument("!connection.active_record", payload) do
1072
+ pool_config.pool
1073
+ end
974
1074
  end
975
1075
 
976
1076
  # Returns true if there are any active connections among the connection
977
1077
  # pools that the ConnectionHandler is managing.
978
- def active_connections?
979
- connection_pool_list.any?(&:active_connection?)
1078
+ def active_connections?(role = ActiveRecord::Base.current_role)
1079
+ connection_pool_list(role).any?(&:active_connection?)
980
1080
  end
981
1081
 
982
1082
  # Returns any connections in use by the current thread back to the pool,
983
1083
  # and also returns connections to the pool cached by threads that are no
984
1084
  # longer alive.
985
- def clear_active_connections!
986
- connection_pool_list.each(&:release_connection)
1085
+ def clear_active_connections!(role = ActiveRecord::Base.current_role)
1086
+ connection_pool_list(role).each(&:release_connection)
987
1087
  end
988
1088
 
989
1089
  # Clears the cache which maps classes.
990
1090
  #
991
1091
  # See ConnectionPool#clear_reloadable_connections! for details.
992
- def clear_reloadable_connections!
993
- connection_pool_list.each(&:clear_reloadable_connections!)
1092
+ def clear_reloadable_connections!(role = ActiveRecord::Base.current_role)
1093
+ connection_pool_list(role).each(&:clear_reloadable_connections!)
994
1094
  end
995
1095
 
996
- def clear_all_connections!
997
- connection_pool_list.each(&:disconnect!)
1096
+ def clear_all_connections!(role = ActiveRecord::Base.current_role)
1097
+ connection_pool_list(role).each(&:disconnect!)
998
1098
  end
999
1099
 
1000
1100
  # Disconnects all currently idle connections.
1001
1101
  #
1002
1102
  # See ConnectionPool#flush! for details.
1003
- def flush_idle_connections!
1004
- connection_pool_list.each(&:flush!)
1103
+ def flush_idle_connections!(role = ActiveRecord::Base.current_role)
1104
+ connection_pool_list(role).each(&:flush!)
1005
1105
  end
1006
1106
 
1007
1107
  # Locate the connection of the nearest super class. This can be an
1008
1108
  # active or defined connection: if it is the latter, it will be
1009
1109
  # opened and set as the active connection for the class it was defined
1010
1110
  # for (not necessarily the current class).
1011
- def retrieve_connection(spec_name) #:nodoc:
1012
- pool = retrieve_connection_pool(spec_name)
1013
- raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool
1111
+ def retrieve_connection(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
1112
+ pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
1113
+
1114
+ unless pool
1115
+ if shard != ActiveRecord::Base.default_shard
1116
+ message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard."
1117
+ elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
1118
+ message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
1119
+ elsif role != ActiveRecord::Base.default_role
1120
+ message = "No connection pool for '#{spec_name}' found for the '#{role}' role."
1121
+ else
1122
+ message = "No connection pool for '#{spec_name}' found."
1123
+ end
1124
+
1125
+ raise ConnectionNotEstablished, message
1126
+ end
1127
+
1014
1128
  pool.connection
1015
1129
  end
1016
1130
 
1017
1131
  # Returns true if a connection that's accessible to this class has
1018
1132
  # already been opened.
1019
- def connected?(spec_name)
1020
- conn = retrieve_connection_pool(spec_name)
1021
- conn && conn.connected?
1133
+ def connected?(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
1134
+ pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
1135
+ pool && pool.connected?
1022
1136
  end
1023
1137
 
1024
1138
  # Remove the connection for this class. This will close the active
1025
1139
  # connection and the defined connection (if they exist). The result
1026
1140
  # can be used as an argument for #establish_connection, for easily
1027
1141
  # re-establishing the connection.
1028
- def remove_connection(spec_name)
1029
- if pool = owner_to_pool.delete(spec_name)
1030
- pool.automatic_reconnect = false
1031
- pool.disconnect!
1032
- pool.spec.config
1142
+ def remove_connection(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
1143
+ remove_connection_pool(owner, role: role, shard: shard)&.configuration_hash
1144
+ end
1145
+ deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash"
1146
+
1147
+ def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
1148
+ if pool_manager = get_pool_manager(owner)
1149
+ pool_config = pool_manager.remove_pool_config(role, shard)
1150
+
1151
+ if pool_config
1152
+ pool_config.disconnect!
1153
+ pool_config.db_config
1154
+ end
1033
1155
  end
1034
1156
  end
1035
1157
 
1036
- # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
1158
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
1037
1159
  # This makes retrieving the connection pool O(1) once the process is warm.
1038
1160
  # When a connection is established or removed, we invalidate the cache.
1039
- def retrieve_connection_pool(spec_name)
1040
- owner_to_pool.fetch(spec_name) do
1041
- # Check if a connection was previously established in an ancestor process,
1042
- # which may have been forked.
1043
- if ancestor_pool = pool_from_any_process_for(spec_name)
1044
- # A connection was established in an ancestor process that must have
1045
- # subsequently forked. We can't reuse the connection, but we can copy
1046
- # the specification and establish a new connection with it.
1047
- establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
1048
- pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
1049
- end
1050
- else
1051
- owner_to_pool[spec_name] = nil
1052
- end
1053
- end
1161
+ def retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
1162
+ pool_config = get_pool_manager(owner)&.get_pool_config(role, shard)
1163
+ pool_config&.pool
1054
1164
  end
1055
1165
 
1056
1166
  private
1167
+ attr_reader :owner_to_pool_manager
1057
1168
 
1058
- def owner_to_pool
1059
- @owner_to_pool[Process.pid]
1169
+ # Returns the pool manager for an owner.
1170
+ #
1171
+ # Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is
1172
+ # deprecated in favor of looking it up by `"ActiveRecord::Base"`.
1173
+ #
1174
+ # During the deprecation period, if `"primary"` is passed, the pool manager
1175
+ # for `ActiveRecord::Base` will still be returned.
1176
+ def get_pool_manager(owner)
1177
+ return owner_to_pool_manager[owner] if owner_to_pool_manager.key?(owner)
1178
+
1179
+ if owner == "primary"
1180
+ ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 6.2.0. Please use `ActiveRecord::Base`.")
1181
+ owner_to_pool_manager[Base.name]
1182
+ end
1060
1183
  end
1061
1184
 
1062
- def pool_from_any_process_for(spec_name)
1063
- owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
1064
- owner_to_pool && owner_to_pool[spec_name]
1185
+ # Returns an instance of PoolConfig for a given adapter.
1186
+ # Accepts a hash one layer deep that contains all connection information.
1187
+ #
1188
+ # == Example
1189
+ #
1190
+ # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
1191
+ # pool_config = Base.configurations.resolve_pool_config(:production)
1192
+ # pool_config.db_config.configuration_hash
1193
+ # # => { host: "localhost", database: "foo", adapter: "sqlite3" }
1194
+ #
1195
+ def resolve_pool_config(config, owner_name)
1196
+ db_config = Base.configurations.resolve(config)
1197
+
1198
+ raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
1199
+
1200
+ # Require the adapter itself and give useful feedback about
1201
+ # 1. Missing adapter gems and
1202
+ # 2. Adapter gems' missing dependencies.
1203
+ path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
1204
+ begin
1205
+ require path_to_adapter
1206
+ rescue LoadError => e
1207
+ # We couldn't require the adapter itself. Raise an exception that
1208
+ # points out config typos and missing gems.
1209
+ if e.path == path_to_adapter
1210
+ # We can assume that a non-builtin adapter was specified, so it's
1211
+ # either misspelled or missing from Gemfile.
1212
+ 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
1213
+
1214
+ # Bubbled up from the adapter require. Prefix the exception message
1215
+ # with some guidance about how to address it and reraise.
1216
+ else
1217
+ raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
1218
+ end
1219
+ end
1220
+
1221
+ unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
1222
+ raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
1223
+ end
1224
+
1225
+ ConnectionAdapters::PoolConfig.new(owner_name, db_config)
1065
1226
  end
1066
1227
  end
1067
1228
  end