activerecord 4.2.9 → 6.1.4.1

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 (374) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +964 -1382
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +15 -14
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +266 -251
  8. data/lib/active_record/association_relation.rb +40 -15
  9. data/lib/active_record/associations/alias_tracker.rb +40 -43
  10. data/lib/active_record/associations/association.rb +162 -69
  11. data/lib/active_record/associations/association_scope.rb +105 -130
  12. data/lib/active_record/associations/belongs_to_association.rb +83 -65
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  14. data/lib/active_record/associations/builder/association.rb +57 -43
  15. data/lib/active_record/associations/builder/belongs_to.rb +74 -57
  16. data/lib/active_record/associations/builder/collection_association.rb +15 -37
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +49 -66
  18. data/lib/active_record/associations/builder/has_many.rb +13 -5
  19. data/lib/active_record/associations/builder/has_one.rb +44 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  21. data/lib/active_record/associations/collection_association.rb +148 -287
  22. data/lib/active_record/associations/collection_proxy.rb +252 -150
  23. data/lib/active_record/associations/foreign_association.rb +23 -1
  24. data/lib/active_record/associations/has_many_association.rb +56 -98
  25. data/lib/active_record/associations/has_many_through_association.rb +68 -89
  26. data/lib/active_record/associations/has_one_association.rb +73 -47
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +54 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  31. data/lib/active_record/associations/join_dependency.rb +174 -169
  32. data/lib/active_record/associations/preloader/association.rb +108 -115
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +97 -94
  35. data/lib/active_record/associations/singular_association.rb +18 -39
  36. data/lib/active_record/associations/through_association.rb +39 -19
  37. data/lib/active_record/associations.rb +1845 -1598
  38. data/lib/active_record/attribute_assignment.rb +59 -185
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +18 -10
  40. data/lib/active_record/attribute_methods/dirty.rb +168 -148
  41. data/lib/active_record/attribute_methods/primary_key.rb +93 -83
  42. data/lib/active_record/attribute_methods/query.rb +8 -10
  43. data/lib/active_record/attribute_methods/read.rb +19 -79
  44. data/lib/active_record/attribute_methods/serialization.rb +49 -24
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +55 -36
  46. data/lib/active_record/attribute_methods/write.rb +24 -55
  47. data/lib/active_record/attribute_methods.rb +149 -154
  48. data/lib/active_record/attributes.rb +234 -78
  49. data/lib/active_record/autosave_association.rb +133 -60
  50. data/lib/active_record/base.rb +46 -46
  51. data/lib/active_record/callbacks.rb +234 -79
  52. data/lib/active_record/coders/json.rb +3 -1
  53. data/lib/active_record/coders/yaml_column.rb +34 -13
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -323
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +292 -124
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +177 -60
  59. data/lib/active_record/connection_adapters/abstract/savepoints.rb +8 -6
  60. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +473 -255
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +869 -286
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +257 -91
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +483 -230
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +557 -640
  67. data/lib/active_record/connection_adapters/column.rb +67 -40
  68. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  69. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +194 -0
  72. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +96 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +97 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +103 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +91 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  78. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +80 -192
  80. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  81. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -160
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +8 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -19
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -20
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  98. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  101. data/lib/active_record/connection_adapters/postgresql/oid/{infinity.rb → oid.rb} +5 -3
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +32 -11
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +70 -34
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  109. data/lib/active_record/connection_adapters/postgresql/oid.rb +25 -25
  110. data/lib/active_record/connection_adapters/postgresql/quoting.rb +145 -48
  111. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  112. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  114. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +49 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +496 -298
  116. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  117. data/lib/active_record/connection_adapters/postgresql/utils.rb +11 -8
  118. data/lib/active_record/connection_adapters/postgresql_adapter.rb +588 -375
  119. data/lib/active_record/connection_adapters/schema_cache.rb +167 -29
  120. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  121. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  122. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  123. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +102 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +21 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +322 -373
  129. data/lib/active_record/connection_adapters/statement_pool.rb +33 -13
  130. data/lib/active_record/connection_adapters.rb +52 -0
  131. data/lib/active_record/connection_handling.rb +314 -41
  132. data/lib/active_record/core.rb +458 -241
  133. data/lib/active_record/counter_cache.rb +70 -49
  134. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  135. data/lib/active_record/database_configurations/database_config.rb +80 -0
  136. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  137. data/lib/active_record/database_configurations/url_config.rb +53 -0
  138. data/lib/active_record/database_configurations.rb +272 -0
  139. data/lib/active_record/delegated_type.rb +209 -0
  140. data/lib/active_record/destroy_association_async_job.rb +36 -0
  141. data/lib/active_record/dynamic_matchers.rb +87 -106
  142. data/lib/active_record/enum.rb +211 -92
  143. data/lib/active_record/errors.rb +224 -54
  144. data/lib/active_record/explain.rb +27 -11
  145. data/lib/active_record/explain_registry.rb +4 -2
  146. data/lib/active_record/explain_subscriber.rb +10 -5
  147. data/lib/active_record/fixture_set/file.rb +33 -14
  148. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  149. data/lib/active_record/fixture_set/render_context.rb +17 -0
  150. data/lib/active_record/fixture_set/table_row.rb +152 -0
  151. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  152. data/lib/active_record/fixtures.rb +275 -500
  153. data/lib/active_record/gem_version.rb +6 -4
  154. data/lib/active_record/inheritance.rb +175 -110
  155. data/lib/active_record/insert_all.rb +212 -0
  156. data/lib/active_record/integration.rb +121 -29
  157. data/lib/active_record/internal_metadata.rb +62 -0
  158. data/lib/active_record/legacy_yaml_adapter.rb +27 -5
  159. data/lib/active_record/locale/en.yml +3 -2
  160. data/lib/active_record/locking/optimistic.rb +98 -92
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +93 -31
  163. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector.rb +77 -0
  166. data/lib/active_record/migration/command_recorder.rb +185 -90
  167. data/lib/active_record/migration/compatibility.rb +295 -0
  168. data/lib/active_record/migration/join_table.rb +8 -7
  169. data/lib/active_record/migration.rb +673 -325
  170. data/lib/active_record/model_schema.rb +418 -113
  171. data/lib/active_record/nested_attributes.rb +263 -224
  172. data/lib/active_record/no_touching.rb +15 -2
  173. data/lib/active_record/null_relation.rb +24 -38
  174. data/lib/active_record/persistence.rb +572 -136
  175. data/lib/active_record/query_cache.rb +29 -23
  176. data/lib/active_record/querying.rb +50 -31
  177. data/lib/active_record/railtie.rb +170 -51
  178. data/lib/active_record/railties/console_sandbox.rb +3 -3
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +523 -199
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +454 -291
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +85 -0
  184. data/lib/active_record/relation/batches.rb +217 -59
  185. data/lib/active_record/relation/calculations.rb +324 -249
  186. data/lib/active_record/relation/delegation.rb +76 -84
  187. data/lib/active_record/relation/finder_methods.rb +316 -242
  188. data/lib/active_record/relation/from_clause.rb +30 -0
  189. data/lib/active_record/relation/merger.rb +95 -103
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -26
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  192. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  193. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +57 -0
  194. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  195. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  196. data/lib/active_record/relation/predicate_builder.rb +136 -122
  197. data/lib/active_record/relation/query_attribute.rb +50 -0
  198. data/lib/active_record/relation/query_methods.rb +757 -413
  199. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  200. data/lib/active_record/relation/spawn_methods.rb +18 -20
  201. data/lib/active_record/relation/where_clause.rb +239 -0
  202. data/lib/active_record/relation.rb +554 -343
  203. data/lib/active_record/result.rb +91 -47
  204. data/lib/active_record/runtime_registry.rb +6 -4
  205. data/lib/active_record/sanitization.rb +134 -122
  206. data/lib/active_record/schema.rb +21 -24
  207. data/lib/active_record/schema_dumper.rb +141 -92
  208. data/lib/active_record/schema_migration.rb +24 -23
  209. data/lib/active_record/scoping/default.rb +96 -83
  210. data/lib/active_record/scoping/named.rb +78 -36
  211. data/lib/active_record/scoping.rb +45 -27
  212. data/lib/active_record/secure_token.rb +48 -0
  213. data/lib/active_record/serialization.rb +8 -6
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +89 -36
  216. data/lib/active_record/store.rb +128 -43
  217. data/lib/active_record/suppressor.rb +61 -0
  218. data/lib/active_record/table_metadata.rb +81 -0
  219. data/lib/active_record/tasks/database_tasks.rb +364 -130
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +67 -113
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +86 -49
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +287 -0
  225. data/lib/active_record/timestamp.rb +86 -43
  226. data/lib/active_record/touch_later.rb +65 -0
  227. data/lib/active_record/transactions.rb +182 -163
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type/adapter_specific_registry.rb +126 -0
  230. data/lib/active_record/type/date.rb +4 -45
  231. data/lib/active_record/type/date_time.rb +4 -49
  232. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  233. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  234. data/lib/active_record/type/internal/timezone.rb +17 -0
  235. data/lib/active_record/type/json.rb +30 -0
  236. data/lib/active_record/type/serialized.rb +27 -15
  237. data/lib/active_record/type/text.rb +2 -2
  238. data/lib/active_record/type/time.rb +21 -16
  239. data/lib/active_record/type/type_map.rb +16 -19
  240. data/lib/active_record/type/unsigned_integer.rb +9 -8
  241. data/lib/active_record/type.rb +84 -23
  242. data/lib/active_record/type_caster/connection.rb +33 -0
  243. data/lib/active_record/type_caster/map.rb +23 -0
  244. data/lib/active_record/type_caster.rb +9 -0
  245. data/lib/active_record/validations/absence.rb +25 -0
  246. data/lib/active_record/validations/associated.rb +12 -4
  247. data/lib/active_record/validations/length.rb +26 -0
  248. data/lib/active_record/validations/numericality.rb +35 -0
  249. data/lib/active_record/validations/presence.rb +14 -13
  250. data/lib/active_record/validations/uniqueness.rb +63 -56
  251. data/lib/active_record/validations.rb +39 -35
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/active_record.rb +42 -29
  254. data/lib/arel/alias_predication.rb +9 -0
  255. data/lib/arel/attributes/attribute.rb +41 -0
  256. data/lib/arel/collectors/bind.rb +29 -0
  257. data/lib/arel/collectors/composite.rb +39 -0
  258. data/lib/arel/collectors/plain_string.rb +20 -0
  259. data/lib/arel/collectors/sql_string.rb +27 -0
  260. data/lib/arel/collectors/substitute_binds.rb +35 -0
  261. data/lib/arel/crud.rb +42 -0
  262. data/lib/arel/delete_manager.rb +18 -0
  263. data/lib/arel/errors.rb +9 -0
  264. data/lib/arel/expressions.rb +29 -0
  265. data/lib/arel/factory_methods.rb +49 -0
  266. data/lib/arel/insert_manager.rb +49 -0
  267. data/lib/arel/math.rb +45 -0
  268. data/lib/arel/nodes/and.rb +32 -0
  269. data/lib/arel/nodes/ascending.rb +23 -0
  270. data/lib/arel/nodes/binary.rb +126 -0
  271. data/lib/arel/nodes/bind_param.rb +44 -0
  272. data/lib/arel/nodes/case.rb +55 -0
  273. data/lib/arel/nodes/casted.rb +62 -0
  274. data/lib/arel/nodes/comment.rb +29 -0
  275. data/lib/arel/nodes/count.rb +12 -0
  276. data/lib/arel/nodes/delete_statement.rb +45 -0
  277. data/lib/arel/nodes/descending.rb +23 -0
  278. data/lib/arel/nodes/equality.rb +15 -0
  279. data/lib/arel/nodes/extract.rb +24 -0
  280. data/lib/arel/nodes/false.rb +16 -0
  281. data/lib/arel/nodes/full_outer_join.rb +8 -0
  282. data/lib/arel/nodes/function.rb +44 -0
  283. data/lib/arel/nodes/grouping.rb +11 -0
  284. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  285. data/lib/arel/nodes/in.rb +15 -0
  286. data/lib/arel/nodes/infix_operation.rb +92 -0
  287. data/lib/arel/nodes/inner_join.rb +8 -0
  288. data/lib/arel/nodes/insert_statement.rb +37 -0
  289. data/lib/arel/nodes/join_source.rb +20 -0
  290. data/lib/arel/nodes/matches.rb +18 -0
  291. data/lib/arel/nodes/named_function.rb +23 -0
  292. data/lib/arel/nodes/node.rb +51 -0
  293. data/lib/arel/nodes/node_expression.rb +13 -0
  294. data/lib/arel/nodes/ordering.rb +27 -0
  295. data/lib/arel/nodes/outer_join.rb +8 -0
  296. data/lib/arel/nodes/over.rb +15 -0
  297. data/lib/arel/nodes/regexp.rb +16 -0
  298. data/lib/arel/nodes/right_outer_join.rb +8 -0
  299. data/lib/arel/nodes/select_core.rb +67 -0
  300. data/lib/arel/nodes/select_statement.rb +41 -0
  301. data/lib/arel/nodes/sql_literal.rb +19 -0
  302. data/lib/arel/nodes/string_join.rb +11 -0
  303. data/lib/arel/nodes/table_alias.rb +31 -0
  304. data/lib/arel/nodes/terminal.rb +16 -0
  305. data/lib/arel/nodes/true.rb +16 -0
  306. data/lib/arel/nodes/unary.rb +44 -0
  307. data/lib/arel/nodes/unary_operation.rb +20 -0
  308. data/lib/arel/nodes/unqualified_column.rb +22 -0
  309. data/lib/arel/nodes/update_statement.rb +41 -0
  310. data/lib/arel/nodes/values_list.rb +9 -0
  311. data/lib/arel/nodes/window.rb +126 -0
  312. data/lib/arel/nodes/with.rb +11 -0
  313. data/lib/arel/nodes.rb +70 -0
  314. data/lib/arel/order_predications.rb +13 -0
  315. data/lib/arel/predications.rb +250 -0
  316. data/lib/arel/select_manager.rb +270 -0
  317. data/lib/arel/table.rb +118 -0
  318. data/lib/arel/tree_manager.rb +72 -0
  319. data/lib/arel/update_manager.rb +34 -0
  320. data/lib/arel/visitors/dot.rb +308 -0
  321. data/lib/arel/visitors/mysql.rb +93 -0
  322. data/lib/arel/visitors/postgresql.rb +120 -0
  323. data/lib/arel/visitors/sqlite.rb +38 -0
  324. data/lib/arel/visitors/to_sql.rb +899 -0
  325. data/lib/arel/visitors/visitor.rb +45 -0
  326. data/lib/arel/visitors.rb +13 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/arel.rb +54 -0
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  331. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -37
  332. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +26 -0
  333. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +13 -4
  334. data/lib/rails/generators/active_record/migration.rb +35 -1
  335. data/lib/rails/generators/active_record/model/model_generator.rb +55 -22
  336. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  337. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. data/lib/rails/generators/active_record.rb +7 -5
  340. metadata +172 -65
  341. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  342. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  343. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  344. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  345. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  346. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  347. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  348. data/lib/active_record/attribute.rb +0 -163
  349. data/lib/active_record/attribute_decorators.rb +0 -66
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/attribute_set.rb +0 -81
  352. data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
  353. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  354. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  355. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  356. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  357. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  358. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  359. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  360. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  361. data/lib/active_record/type/big_integer.rb +0 -13
  362. data/lib/active_record/type/binary.rb +0 -50
  363. data/lib/active_record/type/boolean.rb +0 -31
  364. data/lib/active_record/type/decimal.rb +0 -64
  365. data/lib/active_record/type/decorator.rb +0 -14
  366. data/lib/active_record/type/float.rb +0 -19
  367. data/lib/active_record/type/integer.rb +0 -59
  368. data/lib/active_record/type/mutable.rb +0 -16
  369. data/lib/active_record/type/numeric.rb +0 -36
  370. data/lib/active_record/type/string.rb +0 -40
  371. data/lib/active_record/type/time_value.rb +0 -38
  372. data/lib/active_record/type/value.rb +0 -110
  373. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  374. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,17 +1,34 @@
1
- require 'thread'
2
- require 'thread_safe'
3
- require 'monitor'
4
- require 'set'
5
- require 'active_support/core_ext/string/filters'
1
+ # frozen_string_literal: true
6
2
 
7
- 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
3
+ require "thread"
4
+ require "concurrent/map"
5
+ require "monitor"
6
+ require "weakref"
13
7
 
8
+ module ActiveRecord
14
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
16
+
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 connection_klass
28
+ nil
29
+ end
30
+ end
31
+
15
32
  # Connection pool base class for managing Active Record database
16
33
  # connections.
17
34
  #
@@ -33,17 +50,18 @@ module ActiveRecord
33
50
  # Connections can be obtained and used from a connection pool in several
34
51
  # ways:
35
52
  #
36
- # 1. Simply use ActiveRecord::Base.connection as with Active Record 2.1 and
53
+ # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
54
+ # as with Active Record 2.1 and
37
55
  # earlier (pre-connection-pooling). Eventually, when you're done with
38
56
  # the connection(s) and wish it to be returned to the pool, you call
39
- # ActiveRecord::Base.clear_active_connections!. This will be the
40
- # default behavior for Active Record when used in conjunction with
57
+ # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
58
+ # This will be the default behavior for Active Record when used in conjunction with
41
59
  # Action Pack's request handling cycle.
42
60
  # 2. Manually check out a connection from the pool with
43
- # ActiveRecord::Base.connection_pool.checkout. You are responsible for
61
+ # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
44
62
  # returning this connection to the pool when finished by calling
45
- # ActiveRecord::Base.connection_pool.checkin(connection).
46
- # 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
63
+ # {ActiveRecord::Base.connection_pool.checkin(connection)}[rdoc-ref:#checkin].
64
+ # 3. Use {ActiveRecord::Base.connection_pool.with_connection(&block)}[rdoc-ref:#with_connection], which
47
65
  # obtains a connection, yields it as the sole argument to the block,
48
66
  # and returns it to the pool after the block completes.
49
67
  #
@@ -55,21 +73,25 @@ module ActiveRecord
55
73
  # There are several connection-pooling-related options that you can add to
56
74
  # your database connection configuration:
57
75
  #
58
- # * +pool+: number indicating size of connection pool (default 5)
59
- # * +checkout_timeout+: number of seconds to block and wait for a connection
60
- # before giving up and raising a timeout error (default 5 seconds).
61
- # * +reaping_frequency+: frequency in seconds to periodically run the
62
- # Reaper, which attempts to find and recover connections from dead
63
- # threads, which can occur if a programmer forgets to close a
64
- # connection at the end of a thread or a thread dies unexpectedly.
65
- # Regardless of this setting, the Reaper will be invoked before every
66
- # blocking wait. (Default nil, which means don't schedule the Reaper).
76
+ # * +pool+: maximum number of connections the pool may manage (default 5).
77
+ # * +idle_timeout+: number of seconds that a connection will be kept
78
+ # unused in the pool before it is automatically disconnected (default
79
+ # 300 seconds). Set this to zero to keep connections forever.
80
+ # * +checkout_timeout+: number of seconds to wait for a connection to
81
+ # become available before giving up and raising a timeout error (default
82
+ # 5 seconds).
83
+ #
84
+ #--
85
+ # Synchronization policy:
86
+ # * all public methods can be called outside +synchronize+
87
+ # * access to these instance variables needs to be in +synchronize+:
88
+ # * @connections
89
+ # * @now_connecting
90
+ # * private methods that require being called in a +synchronize+ blocks
91
+ # are now explicitly documented
67
92
  class ConnectionPool
68
- # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
69
- # with which it shares a Monitor. But could be a generic Queue.
70
- #
71
- # The Queue in stdlib's 'thread' could replace this class except
72
- # stdlib's doesn't support waiting with a timeout.
93
+ # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
94
+ # with which it shares a Monitor.
73
95
  class Queue
74
96
  def initialize(lock = Monitor.new)
75
97
  @lock = lock
@@ -101,7 +123,7 @@ module ActiveRecord
101
123
  end
102
124
  end
103
125
 
104
- # If +element+ is in the queue, remove and return it, or nil.
126
+ # If +element+ is in the queue, remove and return it, or +nil+.
105
127
  def delete(element)
106
128
  synchronize do
107
129
  @queue.delete(element)
@@ -117,89 +139,162 @@ module ActiveRecord
117
139
 
118
140
  # Remove the head of the queue.
119
141
  #
120
- # If +timeout+ is not given, remove and return the head the
142
+ # If +timeout+ is not given, remove and return the head of the
121
143
  # queue if the number of available elements is strictly
122
144
  # greater than the number of threads currently waiting (that
123
- # is, don't jump ahead in line). Otherwise, return nil.
145
+ # is, don't jump ahead in line). Otherwise, return +nil+.
124
146
  #
125
- # If +timeout+ is given, block if it there is no element
147
+ # If +timeout+ is given, block if there is no element
126
148
  # available, waiting up to +timeout+ seconds for an element to
127
149
  # become available.
128
150
  #
129
151
  # Raises:
130
- # - ConnectionTimeoutError if +timeout+ is given and no element
131
- # becomes available after +timeout+ seconds,
152
+ # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
153
+ # becomes available within +timeout+ seconds,
132
154
  def poll(timeout = nil)
133
- synchronize do
134
- if timeout
135
- no_wait_poll || wait_poll(timeout)
136
- else
137
- no_wait_poll
138
- end
139
- end
155
+ synchronize { internal_poll(timeout) }
140
156
  end
141
157
 
142
158
  private
159
+ def internal_poll(timeout)
160
+ no_wait_poll || (timeout && wait_poll(timeout))
161
+ end
143
162
 
144
- def synchronize(&block)
145
- @lock.synchronize(&block)
146
- end
163
+ def synchronize(&block)
164
+ @lock.synchronize(&block)
165
+ end
147
166
 
148
- # Test if the queue currently contains any elements.
149
- def any?
150
- !@queue.empty?
151
- end
167
+ # Test if the queue currently contains any elements.
168
+ def any?
169
+ !@queue.empty?
170
+ end
152
171
 
153
- # A thread can remove an element from the queue without
154
- # waiting if an only if the number of currently available
155
- # connections is strictly greater than the number of waiting
156
- # threads.
157
- def can_remove_no_wait?
158
- @queue.size > @num_waiting
159
- end
172
+ # A thread can remove an element from the queue without
173
+ # waiting if and only if the number of currently available
174
+ # connections is strictly greater than the number of waiting
175
+ # threads.
176
+ def can_remove_no_wait?
177
+ @queue.size > @num_waiting
178
+ end
160
179
 
161
- # Removes and returns the head of the queue if possible, or nil.
162
- def remove
163
- @queue.shift
164
- end
180
+ # Removes and returns the head of the queue if possible, or +nil+.
181
+ def remove
182
+ @queue.pop
183
+ end
165
184
 
166
- # Remove and return the head the queue if the number of
167
- # available elements is strictly greater than the number of
168
- # threads currently waiting. Otherwise, return nil.
169
- def no_wait_poll
170
- remove if can_remove_no_wait?
171
- end
185
+ # Remove and return the head of the queue if the number of
186
+ # available elements is strictly greater than the number of
187
+ # threads currently waiting. Otherwise, return +nil+.
188
+ def no_wait_poll
189
+ remove if can_remove_no_wait?
190
+ end
172
191
 
173
- # Waits on the queue up to +timeout+ seconds, then removes and
174
- # returns the head of the queue.
175
- def wait_poll(timeout)
176
- @num_waiting += 1
192
+ # Waits on the queue up to +timeout+ seconds, then removes and
193
+ # returns the head of the queue.
194
+ def wait_poll(timeout)
195
+ @num_waiting += 1
196
+
197
+ t0 = Concurrent.monotonic_time
198
+ elapsed = 0
199
+ loop do
200
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
201
+ @cond.wait(timeout - elapsed)
202
+ end
203
+
204
+ return remove if any?
205
+
206
+ elapsed = Concurrent.monotonic_time - t0
207
+ if elapsed >= timeout
208
+ msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
209
+ [timeout, elapsed]
210
+ raise ConnectionTimeoutError, msg
211
+ end
212
+ end
213
+ ensure
214
+ @num_waiting -= 1
215
+ end
216
+ end
177
217
 
178
- t0 = Time.now
179
- elapsed = 0
180
- loop do
181
- @cond.wait(timeout - elapsed)
218
+ # Adds the ability to turn a basic fair FIFO queue into one
219
+ # biased to some thread.
220
+ module BiasableQueue # :nodoc:
221
+ class BiasedConditionVariable # :nodoc:
222
+ # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
223
+ # +signal+ and +wait+ methods are only called while holding a lock
224
+ def initialize(lock, other_cond, preferred_thread)
225
+ @real_cond = lock.new_cond
226
+ @other_cond = other_cond
227
+ @preferred_thread = preferred_thread
228
+ @num_waiting_on_real_cond = 0
229
+ end
182
230
 
183
- return remove if any?
231
+ def broadcast
232
+ broadcast_on_biased
233
+ @other_cond.broadcast
234
+ end
184
235
 
185
- elapsed = Time.now - t0
186
- if elapsed >= timeout
187
- msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
188
- [timeout, elapsed]
189
- raise ConnectionTimeoutError, msg
190
- end
236
+ def broadcast_on_biased
237
+ @num_waiting_on_real_cond = 0
238
+ @real_cond.broadcast
191
239
  end
240
+
241
+ def signal
242
+ if @num_waiting_on_real_cond > 0
243
+ @num_waiting_on_real_cond -= 1
244
+ @real_cond
245
+ else
246
+ @other_cond
247
+ end.signal
248
+ end
249
+
250
+ def wait(timeout)
251
+ if Thread.current == @preferred_thread
252
+ @num_waiting_on_real_cond += 1
253
+ @real_cond
254
+ else
255
+ @other_cond
256
+ end.wait(timeout)
257
+ end
258
+ end
259
+
260
+ def with_a_bias_for(thread)
261
+ previous_cond = nil
262
+ new_cond = nil
263
+ synchronize do
264
+ previous_cond = @cond
265
+ @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
266
+ end
267
+ yield
192
268
  ensure
193
- @num_waiting -= 1
269
+ synchronize do
270
+ @cond = previous_cond if previous_cond
271
+ new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
272
+ end
194
273
  end
195
274
  end
196
275
 
197
- # Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
198
- # A reaper instantiated with a nil frequency will never reap the
199
- # connection pool.
276
+ # Connections must be leased while holding the main pool mutex. This is
277
+ # an internal subclass that also +.leases+ returned connections while
278
+ # still in queue's critical section (queue synchronizes with the same
279
+ # <tt>@lock</tt> as the main pool) so that a returned connection is already
280
+ # leased and there is no need to re-enter synchronized block.
281
+ class ConnectionLeasingQueue < Queue # :nodoc:
282
+ include BiasableQueue
283
+
284
+ private
285
+ def internal_poll(timeout)
286
+ conn = super
287
+ conn.lease if conn
288
+ conn
289
+ end
290
+ end
291
+
292
+ # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
293
+ # +pool+. A reaper instantiated with a zero frequency will never reap
294
+ # the connection pool.
200
295
  #
201
- # Configure the frequency by setting "reaping_frequency" in your
202
- # database yaml file.
296
+ # Configure the frequency by setting +reaping_frequency+ in your database
297
+ # yaml file (default 60 seconds).
203
298
  class Reaper
204
299
  attr_reader :pool, :frequency
205
300
 
@@ -208,90 +303,165 @@ module ActiveRecord
208
303
  @frequency = frequency
209
304
  end
210
305
 
211
- def run
212
- return unless frequency
213
- Thread.new(frequency, pool) { |t, p|
214
- while true
215
- sleep t
216
- p.reap
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
217
349
  end
218
- }
350
+ end
351
+
352
+ def run
353
+ return unless frequency && frequency > 0
354
+ self.class.register_pool(pool, frequency)
219
355
  end
220
356
  end
221
357
 
222
358
  include MonitorMixin
359
+ include QueryCache::ConnectionPoolConfiguration
360
+ include ConnectionAdapters::AbstractPool
223
361
 
224
362
  attr_accessor :automatic_reconnect, :checkout_timeout
225
- attr_reader :spec, :connections, :size, :reaper
363
+ attr_reader :db_config, :size, :reaper, :pool_config, :connection_klass
226
364
 
227
- # 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
228
368
  # object which describes database connection information (e.g. adapter,
229
369
  # host name, username, password, etc), as well as the maximum size for
230
370
  # this ConnectionPool.
231
371
  #
232
372
  # The default ConnectionPool maximum size is 5.
233
- def initialize(spec)
373
+ def initialize(pool_config)
234
374
  super()
235
375
 
236
- @spec = spec
376
+ @pool_config = pool_config
377
+ @db_config = pool_config.db_config
378
+ @connection_klass = pool_config.connection_klass
379
+
380
+ @checkout_timeout = db_config.checkout_timeout
381
+ @idle_timeout = db_config.idle_timeout
382
+ @size = db_config.pool
383
+
384
+ # This variable tracks the cache of threads mapped to reserved connections, with the
385
+ # sole purpose of speeding up the +connection+ method. It is not the authoritative
386
+ # registry of which thread owns which connection. Connection ownership is tracked by
387
+ # the +connection.owner+ attr on each +connection+ instance.
388
+ # The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
389
+ # then that +thread+ does indeed own that +conn+. However, an absence of such
390
+ # mapping does not mean that the +thread+ doesn't own the said connection. In
391
+ # that case +conn.owner+ attr should be consulted.
392
+ # Access and modification of <tt>@thread_cached_conns</tt> does not require
393
+ # synchronization.
394
+ @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
237
395
 
238
- @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
239
- @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
240
- @reaper.run
396
+ @connections = []
397
+ @automatic_reconnect = true
241
398
 
242
- # default max pool size to 5
243
- @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
399
+ # Connection pool allows for concurrent (outside the main +synchronize+ section)
400
+ # establishment of new connections. This variable tracks the number of threads
401
+ # currently in the process of independently establishing connections to the DB.
402
+ @now_connecting = 0
244
403
 
245
- # The cache of reserved connections mapped to threads
246
- @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
404
+ @threads_blocking_new_connections = 0
247
405
 
248
- @connections = []
249
- @automatic_reconnect = true
406
+ @available = ConnectionLeasingQueue.new self
250
407
 
251
- @available = Queue.new self
408
+ @lock_thread = false
409
+
410
+ @reaper = Reaper.new(self, db_config.reaping_frequency)
411
+ @reaper.run
412
+ end
413
+
414
+ def lock_thread=(lock_thread)
415
+ if lock_thread
416
+ @lock_thread = Thread.current
417
+ else
418
+ @lock_thread = nil
419
+ end
252
420
  end
253
421
 
254
422
  # Retrieve the connection associated with the current thread, or call
255
423
  # #checkout to obtain one if necessary.
256
424
  #
257
425
  # #connection can be called any number of times; the connection is
258
- # held in a hash keyed by the thread id.
426
+ # held in a cache keyed by a thread.
259
427
  def connection
260
- # this is correctly done double-checked locking
261
- # (ThreadSafe::Cache's lookups have volatile semantics)
262
- @reserved_connections[current_connection_id] || synchronize do
263
- @reserved_connections[current_connection_id] ||= checkout
264
- end
428
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
265
429
  end
266
430
 
267
- # Is there an open connection that is being used for the current thread?
431
+ # Returns true if there is an open connection being used for the current thread.
432
+ #
433
+ # This method only works for connections that have been obtained through
434
+ # #connection or #with_connection methods. Connections obtained through
435
+ # #checkout will not be detected by #active_connection?
268
436
  def active_connection?
269
- synchronize do
270
- @reserved_connections.fetch(current_connection_id) {
271
- return false
272
- }.in_use?
273
- end
437
+ @thread_cached_conns[connection_cache_key(current_thread)]
274
438
  end
275
439
 
276
440
  # Signal that the thread is finished with the current connection.
277
441
  # #release_connection releases the connection-thread association
278
442
  # and returns the connection to the pool.
279
- def release_connection(with_id = current_connection_id)
280
- synchronize do
281
- conn = @reserved_connections.delete(with_id)
282
- checkin conn if conn
443
+ #
444
+ # This method only works for connections that have been obtained through
445
+ # #connection or #with_connection methods, connections obtained through
446
+ # #checkout will not be automatically released.
447
+ def release_connection(owner_thread = Thread.current)
448
+ if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
449
+ checkin conn
283
450
  end
284
451
  end
285
452
 
286
- # If a connection already exists yield it to the block. If no connection
453
+ # If a connection obtained through #connection or #with_connection methods
454
+ # already exists yield it to the block. If no such connection
287
455
  # exists checkout a connection, yield it to the block, and checkin the
288
456
  # connection when finished.
289
457
  def with_connection
290
- connection_id = current_connection_id
291
- fresh_connection = true unless active_connection?
292
- yield connection
458
+ unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
459
+ conn = connection
460
+ fresh_connection = true
461
+ end
462
+ yield conn
293
463
  ensure
294
- release_connection(connection_id) if fresh_connection
464
+ release_connection if fresh_connection
295
465
  end
296
466
 
297
467
  # Returns true if a connection has already been opened.
@@ -299,37 +469,107 @@ module ActiveRecord
299
469
  synchronize { @connections.any? }
300
470
  end
301
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
+
302
487
  # Disconnects all connections in the pool, and clears the pool.
303
- def disconnect!
304
- synchronize do
305
- @reserved_connections.clear
306
- @connections.each do |conn|
307
- checkin conn
308
- conn.disconnect!
488
+ #
489
+ # Raises:
490
+ # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
491
+ # connections in the pool within a timeout interval (default duration is
492
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
493
+ def disconnect(raise_on_acquisition_timeout = true)
494
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
495
+ synchronize do
496
+ @connections.each do |conn|
497
+ if conn.in_use?
498
+ conn.steal!
499
+ checkin conn
500
+ end
501
+ conn.disconnect!
502
+ end
503
+ @connections = []
504
+ @available.clear
309
505
  end
310
- @connections = []
311
- @available.clear
312
506
  end
313
507
  end
314
508
 
315
- # Clears the cache which maps classes.
316
- def clear_reloadable_connections!
509
+ # Disconnects all connections in the pool, and clears the pool.
510
+ #
511
+ # The pool first tries to gain ownership of all connections. If unable to
512
+ # do so within a timeout interval (default duration is
513
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool is forcefully
514
+ # disconnected without any regard for other connection owning threads.
515
+ def disconnect!
516
+ disconnect(false)
517
+ end
518
+
519
+ # Discards all connections in the pool (even if they're currently
520
+ # leased!), along with the pool itself. Any further interaction with the
521
+ # pool (except #spec and #schema_cache) is undefined.
522
+ #
523
+ # See AbstractAdapter#discard!
524
+ def discard! # :nodoc:
317
525
  synchronize do
318
- @reserved_connections.clear
526
+ return if self.discarded?
319
527
  @connections.each do |conn|
320
- checkin conn
321
- conn.disconnect! if conn.requires_reloading?
322
- end
323
- @connections.delete_if do |conn|
324
- conn.requires_reloading?
528
+ conn.discard!
325
529
  end
326
- @available.clear
327
- @connections.each do |conn|
328
- @available.add conn
530
+ @connections = @available = @thread_cached_conns = nil
531
+ end
532
+ end
533
+
534
+ def discarded? # :nodoc:
535
+ @connections.nil?
536
+ end
537
+
538
+ # Clears the cache which maps classes and re-connects connections that
539
+ # require reloading.
540
+ #
541
+ # Raises:
542
+ # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
543
+ # connections in the pool within a timeout interval (default duration is
544
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
545
+ def clear_reloadable_connections(raise_on_acquisition_timeout = true)
546
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
547
+ synchronize do
548
+ @connections.each do |conn|
549
+ if conn.in_use?
550
+ conn.steal!
551
+ checkin conn
552
+ end
553
+ conn.disconnect! if conn.requires_reloading?
554
+ end
555
+ @connections.delete_if(&:requires_reloading?)
556
+ @available.clear
329
557
  end
330
558
  end
331
559
  end
332
560
 
561
+ # Clears the cache which maps classes and re-connects connections that
562
+ # require reloading.
563
+ #
564
+ # The pool first tries to gain ownership of all connections. If unable to
565
+ # do so within a timeout interval (default duration is
566
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool forcefully
567
+ # clears the cache and reloads connections without any regard for other
568
+ # connection owning threads.
569
+ def clear_reloadable_connections!
570
+ clear_reloadable_connections(false)
571
+ end
572
+
333
573
  # Check-out a database connection from the pool, indicating that you want
334
574
  # to use it. You should call #checkin when you no longer need this.
335
575
  #
@@ -343,129 +583,363 @@ module ActiveRecord
343
583
  # Returns: an AbstractAdapter object.
344
584
  #
345
585
  # Raises:
346
- # - ConnectionTimeoutError: no connection can be obtained from the pool.
347
- def checkout
348
- synchronize do
349
- conn = acquire_connection
350
- conn.lease
351
- checkout_and_verify(conn)
352
- end
586
+ # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
587
+ def checkout(checkout_timeout = @checkout_timeout)
588
+ checkout_and_verify(acquire_connection(checkout_timeout))
353
589
  end
354
590
 
355
591
  # Check-in a database connection back into the pool, indicating that you
356
592
  # no longer need this connection.
357
593
  #
358
594
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
359
- # calling +checkout+ on this pool.
595
+ # calling #checkout on this pool.
360
596
  def checkin(conn)
361
- synchronize do
362
- owner = conn.owner
363
-
364
- conn._run_checkin_callbacks do
365
- conn.expire
366
- end
597
+ conn.lock.synchronize do
598
+ synchronize do
599
+ remove_connection_from_thread_cache conn
367
600
 
368
- release conn, owner
601
+ conn._run_checkin_callbacks do
602
+ conn.expire
603
+ end
369
604
 
370
- @available.add conn
605
+ @available.add conn
606
+ end
371
607
  end
372
608
  end
373
609
 
374
- # Remove a connection from the connection pool. The connection will
610
+ # Remove a connection from the connection pool. The connection will
375
611
  # remain open and active but will no longer be managed by this pool.
376
612
  def remove(conn)
613
+ needs_new_connection = false
614
+
377
615
  synchronize do
616
+ remove_connection_from_thread_cache conn
617
+
378
618
  @connections.delete conn
379
619
  @available.delete conn
380
620
 
381
- release conn, conn.owner
382
-
383
- @available.add checkout_new_connection if @available.any_waiting?
621
+ # @available.any_waiting? => true means that prior to removing this
622
+ # conn, the pool was at its max size (@connections.size == @size).
623
+ # This would mean that any threads stuck waiting in the queue wouldn't
624
+ # know they could checkout_new_connection, so let's do it for them.
625
+ # Because condition-wait loop is encapsulated in the Queue class
626
+ # (that in turn is oblivious to ConnectionPool implementation), threads
627
+ # that are "stuck" there are helpless. They have no way of creating
628
+ # new connections and are completely reliant on us feeding available
629
+ # connections into the Queue.
630
+ needs_new_connection = @available.any_waiting?
384
631
  end
632
+
633
+ # This is intentionally done outside of the synchronized section as we
634
+ # would like not to hold the main mutex while checking out new connections.
635
+ # Thus there is some chance that needs_new_connection information is now
636
+ # stale, we can live with that (bulk_make_new_connections will make
637
+ # sure not to exceed the pool's @size limit).
638
+ bulk_make_new_connections(1) if needs_new_connection
385
639
  end
386
640
 
387
- # Recover lost connections for the pool. A lost connection can occur if
641
+ # Recover lost connections for the pool. A lost connection can occur if
388
642
  # a programmer forgets to checkin a connection at the end of a thread
389
643
  # or a thread dies unexpectedly.
390
644
  def reap
391
645
  stale_connections = synchronize do
646
+ return if self.discarded?
392
647
  @connections.select do |conn|
393
648
  conn.in_use? && !conn.owner.alive?
649
+ end.each do |conn|
650
+ conn.steal!
394
651
  end
395
652
  end
396
653
 
397
654
  stale_connections.each do |conn|
398
- synchronize do
399
- if conn.active?
400
- conn.reset!
401
- checkin conn
402
- else
403
- remove conn
404
- end
655
+ if conn.active?
656
+ conn.reset!
657
+ checkin conn
658
+ else
659
+ remove conn
405
660
  end
406
661
  end
407
662
  end
408
663
 
409
- private
664
+ # Disconnect all connections that have been idle for at least
665
+ # +minimum_idle+ seconds. Connections currently checked out, or that were
666
+ # checked in less than +minimum_idle+ seconds ago, are unaffected.
667
+ def flush(minimum_idle = @idle_timeout)
668
+ return if minimum_idle.nil?
410
669
 
411
- # Acquire a connection by one of 1) immediately removing one
412
- # from the queue of available connections, 2) creating a new
413
- # connection if the pool is not at capacity, 3) waiting on the
414
- # queue for a connection to become available.
415
- #
416
- # Raises:
417
- # - ConnectionTimeoutError if a connection could not be acquired
418
- def acquire_connection
419
- if conn = @available.poll
420
- conn
421
- elsif @connections.size < @size
422
- checkout_new_connection
423
- else
424
- reap
425
- @available.poll(@checkout_timeout)
426
- end
427
- end
670
+ idle_connections = synchronize do
671
+ return if self.discarded?
672
+ @connections.select do |conn|
673
+ !conn.in_use? && conn.seconds_idle >= minimum_idle
674
+ end.each do |conn|
675
+ conn.lease
428
676
 
429
- def release(conn, owner)
430
- thread_id = owner.object_id
677
+ @available.delete conn
678
+ @connections.delete conn
679
+ end
680
+ end
431
681
 
432
- if @reserved_connections[thread_id] == conn
433
- @reserved_connections.delete thread_id
682
+ idle_connections.each do |conn|
683
+ conn.disconnect!
434
684
  end
435
685
  end
436
686
 
437
- def new_connection
438
- Base.send(spec.adapter_method, spec.config)
687
+ # Disconnect all currently idle connections. Connections currently checked
688
+ # out are unaffected.
689
+ def flush!
690
+ reap
691
+ flush(-1)
439
692
  end
440
693
 
441
- def current_connection_id #:nodoc:
442
- Base.connection_id ||= Thread.current.object_id
694
+ def num_waiting_in_queue # :nodoc:
695
+ @available.num_waiting
443
696
  end
444
697
 
445
- def checkout_new_connection
446
- raise ConnectionNotEstablished unless @automatic_reconnect
447
-
448
- c = new_connection
449
- c.pool = self
450
- @connections << c
451
- c
698
+ # Return connection pool's usage statistic
699
+ # Example:
700
+ #
701
+ # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
702
+ def stat
703
+ synchronize do
704
+ {
705
+ size: size,
706
+ connections: @connections.size,
707
+ busy: @connections.count { |c| c.in_use? && c.owner.alive? },
708
+ dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
709
+ idle: @connections.count { |c| !c.in_use? },
710
+ waiting: num_waiting_in_queue,
711
+ checkout_timeout: checkout_timeout
712
+ }
713
+ end
452
714
  end
453
715
 
454
- def checkout_and_verify(c)
455
- c._run_checkout_callbacks do
456
- c.verify!
716
+ private
717
+ #--
718
+ # this is unfortunately not concurrent
719
+ def bulk_make_new_connections(num_new_conns_needed)
720
+ num_new_conns_needed.times do
721
+ # try_to_checkout_new_connection will not exceed pool's @size limit
722
+ if new_conn = try_to_checkout_new_connection
723
+ # make the new_conn available to the starving threads stuck @available Queue
724
+ checkin(new_conn)
725
+ end
726
+ end
727
+ end
728
+
729
+ #--
730
+ # From the discussion on GitHub:
731
+ # https://github.com/rails/rails/pull/14938#commitcomment-6601951
732
+ # This hook-in method allows for easier monkey-patching fixes needed by
733
+ # JRuby users that use Fibers.
734
+ def connection_cache_key(thread)
735
+ thread
736
+ end
737
+
738
+ def current_thread
739
+ @lock_thread || Thread.current
740
+ end
741
+
742
+ # Take control of all existing connections so a "group" action such as
743
+ # reload/disconnect can be performed safely. It is no longer enough to
744
+ # wrap it in +synchronize+ because some pool's actions are allowed
745
+ # to be performed outside of the main +synchronize+ block.
746
+ def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
747
+ with_new_connections_blocked do
748
+ attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
749
+ yield
750
+ end
751
+ end
752
+
753
+ def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
754
+ collected_conns = synchronize do
755
+ # account for our own connections
756
+ @connections.select { |conn| conn.owner == Thread.current }
757
+ end
758
+
759
+ newly_checked_out = []
760
+ timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
761
+
762
+ @available.with_a_bias_for(Thread.current) do
763
+ loop do
764
+ synchronize do
765
+ return if collected_conns.size == @connections.size && @now_connecting == 0
766
+ remaining_timeout = timeout_time - Concurrent.monotonic_time
767
+ remaining_timeout = 0 if remaining_timeout < 0
768
+ conn = checkout_for_exclusive_access(remaining_timeout)
769
+ collected_conns << conn
770
+ newly_checked_out << conn
771
+ end
772
+ end
773
+ end
774
+ rescue ExclusiveConnectionTimeoutError
775
+ # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
776
+ # timeouts and are expected to just give up: we've obtained as many connections
777
+ # as possible, note that in a case like that we don't return any of the
778
+ # +newly_checked_out+ connections.
779
+
780
+ if raise_on_acquisition_timeout
781
+ release_newly_checked_out = true
782
+ raise
783
+ end
784
+ rescue Exception # if something else went wrong
785
+ # this can't be a "naked" rescue, because we have should return conns
786
+ # even for non-StandardErrors
787
+ release_newly_checked_out = true
788
+ raise
789
+ ensure
790
+ if release_newly_checked_out && newly_checked_out
791
+ # releasing only those conns that were checked out in this method, conns
792
+ # checked outside this method (before it was called) are not for us to release
793
+ newly_checked_out.each { |conn| checkin(conn) }
794
+ end
795
+ end
796
+
797
+ #--
798
+ # Must be called in a synchronize block.
799
+ def checkout_for_exclusive_access(checkout_timeout)
800
+ checkout(checkout_timeout)
801
+ rescue ConnectionTimeoutError
802
+ # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
803
+ # rescue block, because doing so would put it outside of synchronize section, without
804
+ # being in a critical section thread_report might become inaccurate
805
+ msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds"
806
+
807
+ thread_report = []
808
+ @connections.each do |conn|
809
+ unless conn.owner == Thread.current
810
+ thread_report << "#{conn} is owned by #{conn.owner}"
811
+ end
812
+ end
813
+
814
+ msg << " (#{thread_report.join(', ')})" if thread_report.any?
815
+
816
+ raise ExclusiveConnectionTimeoutError, msg
817
+ end
818
+
819
+ def with_new_connections_blocked
820
+ synchronize do
821
+ @threads_blocking_new_connections += 1
822
+ end
823
+
824
+ yield
825
+ ensure
826
+ num_new_conns_required = 0
827
+
828
+ synchronize do
829
+ @threads_blocking_new_connections -= 1
830
+
831
+ if @threads_blocking_new_connections.zero?
832
+ @available.clear
833
+
834
+ num_new_conns_required = num_waiting_in_queue
835
+
836
+ @connections.each do |conn|
837
+ next if conn.in_use?
838
+
839
+ @available.add conn
840
+ num_new_conns_required -= 1
841
+ end
842
+ end
843
+ end
844
+
845
+ bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
846
+ end
847
+
848
+ # Acquire a connection by one of 1) immediately removing one
849
+ # from the queue of available connections, 2) creating a new
850
+ # connection if the pool is not at capacity, 3) waiting on the
851
+ # queue for a connection to become available.
852
+ #
853
+ # Raises:
854
+ # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
855
+ #
856
+ #--
857
+ # Implementation detail: the connection returned by +acquire_connection+
858
+ # will already be "+connection.lease+ -ed" to the current thread.
859
+ def acquire_connection(checkout_timeout)
860
+ # NOTE: we rely on <tt>@available.poll</tt> and +try_to_checkout_new_connection+ to
861
+ # +conn.lease+ the returned connection (and to do this in a +synchronized+
862
+ # section). This is not the cleanest implementation, as ideally we would
863
+ # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
864
+ # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
865
+ # of the said methods and avoid an additional +synchronize+ overhead.
866
+ if conn = @available.poll || try_to_checkout_new_connection
867
+ conn
868
+ else
869
+ reap
870
+ @available.poll(checkout_timeout)
871
+ end
872
+ end
873
+
874
+ #--
875
+ # if owner_thread param is omitted, this must be called in synchronize block
876
+ def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
877
+ @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
878
+ end
879
+ alias_method :release, :remove_connection_from_thread_cache
880
+
881
+ def new_connection
882
+ Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
883
+ conn.check_version
884
+ end
885
+ end
886
+
887
+ # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
888
+ # to the DB is done outside main synchronized section.
889
+ #--
890
+ # Implementation constraint: a newly established connection returned by this
891
+ # method must be in the +.leased+ state.
892
+ def try_to_checkout_new_connection
893
+ # first in synchronized section check if establishing new conns is allowed
894
+ # and increment @now_connecting, to prevent overstepping this pool's @size
895
+ # constraint
896
+ do_checkout = synchronize do
897
+ if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
898
+ @now_connecting += 1
899
+ end
900
+ end
901
+ if do_checkout
902
+ begin
903
+ # if successfully incremented @now_connecting establish new connection
904
+ # outside of synchronized section
905
+ conn = checkout_new_connection
906
+ ensure
907
+ synchronize do
908
+ if conn
909
+ adopt_connection(conn)
910
+ # returned conn needs to be already leased
911
+ conn.lease
912
+ end
913
+ @now_connecting -= 1
914
+ end
915
+ end
916
+ end
917
+ end
918
+
919
+ def adopt_connection(conn)
920
+ conn.pool = self
921
+ @connections << conn
922
+ end
923
+
924
+ def checkout_new_connection
925
+ raise ConnectionNotEstablished unless @automatic_reconnect
926
+ new_connection
927
+ end
928
+
929
+ def checkout_and_verify(c)
930
+ c._run_checkout_callbacks do
931
+ c.verify!
932
+ end
933
+ c
934
+ rescue
935
+ remove c
936
+ c.disconnect!
937
+ raise
457
938
  end
458
- c
459
- rescue
460
- remove c
461
- c.disconnect!
462
- raise
463
- end
464
939
  end
465
940
 
466
941
  # ConnectionHandler is a collection of ConnectionPool objects. It is used
467
- # for keeping separate connection pools for Active Record models that connect
468
- # to different databases.
942
+ # for keeping separate connection pools that connect to different databases.
469
943
  #
470
944
  # For example, suppose that you have 5 models, with the following hierarchy:
471
945
  #
@@ -476,7 +950,7 @@ module ActiveRecord
476
950
  # end
477
951
  #
478
952
  # class Book < ActiveRecord::Base
479
- # establish_connection "library_db"
953
+ # establish_connection :library_db
480
954
  # end
481
955
  #
482
956
  # class ScaryBook < Book
@@ -507,159 +981,249 @@ module ActiveRecord
507
981
  # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
508
982
  # All Active Record models use this handler to determine the connection pool that they
509
983
  # should use.
984
+ #
985
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
986
+ # about the model. The model needs to pass a connection specification name to the handler,
987
+ # in order to look up the correct connection pool.
510
988
  class ConnectionHandler
989
+ FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
990
+ private_constant :FINALIZER
991
+
511
992
  def initialize
512
- # These caches are keyed by klass.name, NOT klass. Keying them by klass
513
- # alone would lead to memory leaks in development mode as all previous
514
- # instances of the class would stay in memory.
515
- @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
516
- h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
517
- end
518
- @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
519
- h[k] = ThreadSafe::Cache.new
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
998
+ end
999
+
1000
+ def prevent_writes # :nodoc:
1001
+ Thread.current[:prevent_writes]
1002
+ end
1003
+
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"
520
1022
  end
1023
+
1024
+ original, self.prevent_writes = self.prevent_writes, enabled
1025
+ yield
1026
+ ensure
1027
+ self.prevent_writes = original
521
1028
  end
522
1029
 
523
- def connection_pool_list
524
- owner_to_pool.values.compact
1030
+ def connection_pool_names # :nodoc:
1031
+ owner_to_pool_manager.keys
525
1032
  end
526
1033
 
527
- def connection_pools
528
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
529
- In the next release, this will return the same as `#connection_pool_list`.
530
- (An array of pools, rather than a hash mapping specs to pools.)
531
- MSG
1034
+ def all_connection_pools
1035
+ owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
1036
+ end
532
1037
 
533
- Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
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) }
534
1040
  end
1041
+ alias :connection_pools :connection_pool_list
1042
+
1043
+ def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
1044
+ owner_name = config.to_s if config.is_a?(Symbol)
535
1045
 
536
- def establish_connection(owner, spec)
537
- @class_to_pool.clear
538
- raise RuntimeError, "Anonymous class is not allowed." unless owner.name
539
- owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
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
1054
+
1055
+ message_bus = ActiveSupport::Notifications.instrumenter
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
1061
+ end
1062
+
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
1067
+ end
1068
+ pool_manager = get_pool_manager(pool_config.connection_specification_name)
1069
+ pool_manager.set_pool_config(role, shard, pool_config)
1070
+
1071
+ message_bus.instrument("!connection.active_record", payload) do
1072
+ pool_config.pool
1073
+ end
540
1074
  end
541
1075
 
542
1076
  # Returns true if there are any active connections among the connection
543
1077
  # pools that the ConnectionHandler is managing.
544
- def active_connections?
545
- connection_pool_list.any?(&:active_connection?)
1078
+ def active_connections?(role = ActiveRecord::Base.current_role)
1079
+ connection_pool_list(role).any?(&:active_connection?)
546
1080
  end
547
1081
 
548
1082
  # Returns any connections in use by the current thread back to the pool,
549
1083
  # and also returns connections to the pool cached by threads that are no
550
1084
  # longer alive.
551
- def clear_active_connections!
552
- connection_pool_list.each(&:release_connection)
1085
+ def clear_active_connections!(role = ActiveRecord::Base.current_role)
1086
+ connection_pool_list(role).each(&:release_connection)
553
1087
  end
554
1088
 
555
1089
  # Clears the cache which maps classes.
556
- def clear_reloadable_connections!
557
- connection_pool_list.each(&:clear_reloadable_connections!)
1090
+ #
1091
+ # See ConnectionPool#clear_reloadable_connections! for details.
1092
+ def clear_reloadable_connections!(role = ActiveRecord::Base.current_role)
1093
+ connection_pool_list(role).each(&:clear_reloadable_connections!)
1094
+ end
1095
+
1096
+ def clear_all_connections!(role = ActiveRecord::Base.current_role)
1097
+ connection_pool_list(role).each(&:disconnect!)
558
1098
  end
559
1099
 
560
- def clear_all_connections!
561
- connection_pool_list.each(&:disconnect!)
1100
+ # Disconnects all currently idle connections.
1101
+ #
1102
+ # See ConnectionPool#flush! for details.
1103
+ def flush_idle_connections!(role = ActiveRecord::Base.current_role)
1104
+ connection_pool_list(role).each(&:flush!)
562
1105
  end
563
1106
 
564
1107
  # Locate the connection of the nearest super class. This can be an
565
1108
  # active or defined connection: if it is the latter, it will be
566
1109
  # opened and set as the active connection for the class it was defined
567
1110
  # for (not necessarily the current class).
568
- def retrieve_connection(klass) #:nodoc:
569
- pool = retrieve_connection_pool(klass)
570
- raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
571
- conn = pool.connection
572
- raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
573
- conn
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
+
1128
+ pool.connection
574
1129
  end
575
1130
 
576
1131
  # Returns true if a connection that's accessible to this class has
577
1132
  # already been opened.
578
- def connected?(klass)
579
- conn = retrieve_connection_pool(klass)
580
- 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?
581
1136
  end
582
1137
 
583
1138
  # Remove the connection for this class. This will close the active
584
1139
  # connection and the defined connection (if they exist). The result
585
- # can be used as an argument for establish_connection, for easily
1140
+ # can be used as an argument for #establish_connection, for easily
586
1141
  # re-establishing the connection.
587
- def remove_connection(owner)
588
- if pool = owner_to_pool.delete(owner.name)
589
- @class_to_pool.clear
590
- pool.automatic_reconnect = false
591
- pool.disconnect!
592
- pool.spec.config
593
- end
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
594
1144
  end
1145
+ deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash"
595
1146
 
596
- # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
597
- # This makes retrieving the connection pool O(1) once the process is warm.
598
- # When a connection is established or removed, we invalidate the cache.
599
- #
600
- # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
601
- # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
602
- # #fetch is significantly slower than #[]. So in the nil case, no caching will
603
- # take place, but that's ok since the nil case is not the common one that we wish
604
- # to optimise for.
605
- def retrieve_connection_pool(klass)
606
- class_to_pool[klass.name] ||= begin
607
- until pool = pool_for(klass)
608
- klass = klass.superclass
609
- break unless klass <= Base
610
- end
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)
611
1150
 
612
- class_to_pool[klass.name] = pool
1151
+ if pool_config
1152
+ pool_config.disconnect!
1153
+ pool_config.db_config
1154
+ end
613
1155
  end
614
1156
  end
615
1157
 
616
- private
617
-
618
- def owner_to_pool
619
- @owner_to_pool[Process.pid]
1158
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
1159
+ # This makes retrieving the connection pool O(1) once the process is warm.
1160
+ # When a connection is established or removed, we invalidate the cache.
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
620
1164
  end
621
1165
 
622
- def class_to_pool
623
- @class_to_pool[Process.pid]
624
- end
1166
+ private
1167
+ attr_reader :owner_to_pool_manager
625
1168
 
626
- def pool_for(owner)
627
- owner_to_pool.fetch(owner.name) {
628
- if ancestor_pool = pool_from_any_process_for(owner)
629
- # A connection was established in an ancestor process that must have
630
- # subsequently forked. We can't reuse the connection, but we can copy
631
- # the specification and establish a new connection with it.
632
- establish_connection owner, ancestor_pool.spec
633
- else
634
- owner_to_pool[owner.name] = nil
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]
635
1182
  end
636
- }
637
- end
638
-
639
- def pool_from_any_process_for(owner)
640
- owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[owner.name] }
641
- owner_to_pool && owner_to_pool[owner.name]
642
- end
643
- end
1183
+ end
644
1184
 
645
- class ConnectionManagement
646
- def initialize(app)
647
- @app = app
648
- end
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
649
1220
 
650
- def call(env)
651
- testing = env['rack.test']
1221
+ unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
1222
+ raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
1223
+ end
652
1224
 
653
- response = @app.call(env)
654
- response[2] = ::Rack::BodyProxy.new(response[2]) do
655
- ActiveRecord::Base.clear_active_connections! unless testing
1225
+ ConnectionAdapters::PoolConfig.new(owner_name, db_config)
656
1226
  end
657
-
658
- response
659
- rescue Exception
660
- ActiveRecord::Base.clear_active_connections! unless testing
661
- raise
662
- end
663
1227
  end
664
1228
  end
665
1229
  end