activerecord 4.2.0 → 6.1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1221 -796
  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 +267 -249
  8. data/lib/active_record/association_relation.rb +45 -7
  9. data/lib/active_record/associations/alias_tracker.rb +40 -43
  10. data/lib/active_record/associations/association.rb +172 -67
  11. data/lib/active_record/associations/association_scope.rb +105 -129
  12. data/lib/active_record/associations/belongs_to_association.rb +85 -59
  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 -33
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -70
  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 +168 -279
  22. data/lib/active_record/associations/collection_proxy.rb +263 -155
  23. data/lib/active_record/associations/foreign_association.rb +33 -0
  24. data/lib/active_record/associations/has_many_association.rb +57 -84
  25. data/lib/active_record/associations/has_many_through_association.rb +70 -82
  26. data/lib/active_record/associations/has_one_association.rb +74 -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 -73
  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 +175 -164
  32. data/lib/active_record/associations/preloader/association.rb +107 -112
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +99 -96
  35. data/lib/active_record/associations/singular_association.rb +18 -45
  36. data/lib/active_record/associations/through_association.rb +49 -24
  37. data/lib/active_record/associations.rb +1845 -1597
  38. data/lib/active_record/attribute_assignment.rb +59 -185
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +20 -7
  40. data/lib/active_record/attribute_methods/dirty.rb +168 -138
  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 +59 -36
  46. data/lib/active_record/attribute_methods/write.rb +25 -56
  47. data/lib/active_record/attribute_methods.rb +153 -162
  48. data/lib/active_record/attributes.rb +234 -70
  49. data/lib/active_record/autosave_association.rb +157 -69
  50. data/lib/active_record/base.rb +49 -50
  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 +46 -13
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -317
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +301 -113
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +187 -60
  59. data/lib/active_record/connection_adapters/abstract/savepoints.rb +9 -7
  60. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +485 -253
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +909 -263
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +254 -92
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +492 -221
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +580 -608
  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 +196 -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 +271 -0
  78. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +81 -199
  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 +78 -161
  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 -57
  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 +5 -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 +17 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +6 -3
  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 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +67 -51
  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 +171 -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 +499 -293
  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 +595 -382
  119. data/lib/active_record/connection_adapters/schema_cache.rb +191 -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 +146 -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 -389
  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 +488 -243
  133. data/lib/active_record/counter_cache.rb +71 -50
  134. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -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 +273 -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 +212 -94
  143. data/lib/active_record/errors.rb +225 -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 +11 -6
  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 +273 -496
  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 +64 -0
  158. data/lib/active_record/legacy_yaml_adapter.rb +52 -0
  159. data/lib/active_record/locale/en.yml +3 -2
  160. data/lib/active_record/locking/optimistic.rb +103 -95
  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 +298 -0
  168. data/lib/active_record/migration/join_table.rb +8 -7
  169. data/lib/active_record/migration.rb +685 -309
  170. data/lib/active_record/model_schema.rb +420 -113
  171. data/lib/active_record/nested_attributes.rb +265 -216
  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 +574 -135
  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 +175 -54
  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 +533 -216
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +485 -310
  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 +326 -244
  186. data/lib/active_record/relation/delegation.rb +76 -84
  187. data/lib/active_record/relation/finder_methods.rb +318 -256
  188. data/lib/active_record/relation/from_clause.rb +30 -0
  189. data/lib/active_record/relation/merger.rb +99 -84
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -25
  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 +139 -96
  197. data/lib/active_record/relation/query_attribute.rb +50 -0
  198. data/lib/active_record/relation/query_methods.rb +757 -409
  199. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  200. data/lib/active_record/relation/spawn_methods.rb +23 -21
  201. data/lib/active_record/relation/where_clause.rb +239 -0
  202. data/lib/active_record/relation.rb +554 -342
  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 -26
  209. data/lib/active_record/scoping/default.rb +96 -82
  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 +133 -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 +366 -129
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +68 -100
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +87 -39
  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 +291 -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 +181 -152
  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 -41
  231. data/lib/active_record/type/date_time.rb +4 -38
  232. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  233. data/lib/active_record/type/hash_lookup_type_map.rb +12 -5
  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 +33 -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 +65 -48
  251. data/lib/active_record/validations.rb +39 -35
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/active_record.rb +44 -28
  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 -10
  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.rb +7 -5
  339. metadata +175 -65
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  342. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  343. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  344. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  347. data/lib/active_record/attribute.rb +0 -149
  348. data/lib/active_record/attribute_decorators.rb +0 -66
  349. data/lib/active_record/attribute_set/builder.rb +0 -86
  350. data/lib/active_record/attribute_set.rb +0 -77
  351. data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
  352. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  353. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  354. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  355. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  356. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  357. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  358. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  359. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  360. data/lib/active_record/type/big_integer.rb +0 -13
  361. data/lib/active_record/type/binary.rb +0 -50
  362. data/lib/active_record/type/boolean.rb +0 -30
  363. data/lib/active_record/type/decimal.rb +0 -40
  364. data/lib/active_record/type/decorator.rb +0 -14
  365. data/lib/active_record/type/float.rb +0 -19
  366. data/lib/active_record/type/integer.rb +0 -55
  367. data/lib/active_record/type/mutable.rb +0 -16
  368. data/lib/active_record/type/numeric.rb +0 -36
  369. data/lib/active_record/type/string.rb +0 -36
  370. data/lib/active_record/type/time_value.rb +0 -38
  371. data/lib/active_record/type/value.rb +0 -101
  372. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -22
  373. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
  374. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -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]
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,123 +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 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.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)
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
676
+
677
+ @available.delete conn
678
+ @connections.delete conn
679
+ end
426
680
  end
427
- end
428
681
 
429
- def release(owner)
430
- thread_id = owner.object_id
682
+ idle_connections.each do |conn|
683
+ conn.disconnect!
684
+ end
685
+ end
431
686
 
432
- @reserved_connections.delete thread_id
687
+ # Disconnect all currently idle connections. Connections currently checked
688
+ # out are unaffected.
689
+ def flush!
690
+ reap
691
+ flush(-1)
433
692
  end
434
693
 
435
- def new_connection
436
- Base.send(spec.adapter_method, spec.config)
694
+ def num_waiting_in_queue # :nodoc:
695
+ @available.num_waiting
437
696
  end
438
697
 
439
- def current_connection_id #:nodoc:
440
- Base.connection_id ||= Thread.current.object_id
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
441
714
  end
442
715
 
443
- def checkout_new_connection
444
- raise ConnectionNotEstablished unless @automatic_reconnect
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
445
737
 
446
- c = new_connection
447
- c.pool = self
448
- @connections << c
449
- c
450
- end
738
+ def current_thread
739
+ @lock_thread || Thread.current
740
+ end
451
741
 
452
- def checkout_and_verify(c)
453
- c._run_checkout_callbacks do
454
- c.verify!
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
455
938
  end
456
- c
457
- end
458
939
  end
459
940
 
460
941
  # ConnectionHandler is a collection of ConnectionPool objects. It is used
461
- # for keeping separate connection pools for Active Record models that connect
462
- # to different databases.
942
+ # for keeping separate connection pools that connect to different databases.
463
943
  #
464
944
  # For example, suppose that you have 5 models, with the following hierarchy:
465
945
  #
@@ -470,7 +950,7 @@ module ActiveRecord
470
950
  # end
471
951
  #
472
952
  # class Book < ActiveRecord::Base
473
- # establish_connection "library_db"
953
+ # establish_connection :library_db
474
954
  # end
475
955
  #
476
956
  # class ScaryBook < Book
@@ -501,159 +981,249 @@ module ActiveRecord
501
981
  # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
502
982
  # All Active Record models use this handler to determine the connection pool that they
503
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.
504
988
  class ConnectionHandler
989
+ FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
990
+ private_constant :FINALIZER
991
+
505
992
  def initialize
506
- # These caches are keyed by klass.name, NOT klass. Keying them by klass
507
- # alone would lead to memory leaks in development mode as all previous
508
- # instances of the class would stay in memory.
509
- @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
510
- h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
511
- end
512
- @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
513
- 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"
514
1022
  end
1023
+
1024
+ original, self.prevent_writes = self.prevent_writes, enabled
1025
+ yield
1026
+ ensure
1027
+ self.prevent_writes = original
515
1028
  end
516
1029
 
517
- def connection_pool_list
518
- owner_to_pool.values.compact
1030
+ def connection_pool_names # :nodoc:
1031
+ owner_to_pool_manager.keys
519
1032
  end
520
1033
 
521
- def connection_pools
522
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
523
- In the next release, this will return the same as `#connection_pool_list`.
524
- (An array of pools, rather than a hash mapping specs to pools.)
525
- MSG
1034
+ def all_connection_pools
1035
+ owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
1036
+ end
526
1037
 
527
- 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) }
528
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)
529
1045
 
530
- def establish_connection(owner, spec)
531
- @class_to_pool.clear
532
- raise RuntimeError, "Anonymous class is not allowed." unless owner.name
533
- 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
534
1074
  end
535
1075
 
536
1076
  # Returns true if there are any active connections among the connection
537
1077
  # pools that the ConnectionHandler is managing.
538
- def active_connections?
539
- connection_pool_list.any?(&:active_connection?)
1078
+ def active_connections?(role = ActiveRecord::Base.current_role)
1079
+ connection_pool_list(role).any?(&:active_connection?)
540
1080
  end
541
1081
 
542
1082
  # Returns any connections in use by the current thread back to the pool,
543
1083
  # and also returns connections to the pool cached by threads that are no
544
1084
  # longer alive.
545
- def clear_active_connections!
546
- connection_pool_list.each(&:release_connection)
1085
+ def clear_active_connections!(role = ActiveRecord::Base.current_role)
1086
+ connection_pool_list(role).each(&:release_connection)
547
1087
  end
548
1088
 
549
1089
  # Clears the cache which maps classes.
550
- def clear_reloadable_connections!
551
- 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!)
552
1098
  end
553
1099
 
554
- def clear_all_connections!
555
- 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!)
556
1105
  end
557
1106
 
558
1107
  # Locate the connection of the nearest super class. This can be an
559
1108
  # active or defined connection: if it is the latter, it will be
560
1109
  # opened and set as the active connection for the class it was defined
561
1110
  # for (not necessarily the current class).
562
- def retrieve_connection(klass) #:nodoc:
563
- pool = retrieve_connection_pool(klass)
564
- raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
565
- conn = pool.connection
566
- raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
567
- 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
568
1129
  end
569
1130
 
570
1131
  # Returns true if a connection that's accessible to this class has
571
1132
  # already been opened.
572
- def connected?(klass)
573
- conn = retrieve_connection_pool(klass)
574
- 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?
575
1136
  end
576
1137
 
577
1138
  # Remove the connection for this class. This will close the active
578
1139
  # connection and the defined connection (if they exist). The result
579
- # can be used as an argument for establish_connection, for easily
1140
+ # can be used as an argument for #establish_connection, for easily
580
1141
  # re-establishing the connection.
581
- def remove_connection(owner)
582
- if pool = owner_to_pool.delete(owner.name)
583
- @class_to_pool.clear
584
- pool.automatic_reconnect = false
585
- pool.disconnect!
586
- pool.spec.config
587
- 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
588
1144
  end
1145
+ deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash"
589
1146
 
590
- # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
591
- # This makes retrieving the connection pool O(1) once the process is warm.
592
- # When a connection is established or removed, we invalidate the cache.
593
- #
594
- # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
595
- # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
596
- # #fetch is significantly slower than #[]. So in the nil case, no caching will
597
- # take place, but that's ok since the nil case is not the common one that we wish
598
- # to optimise for.
599
- def retrieve_connection_pool(klass)
600
- class_to_pool[klass.name] ||= begin
601
- until pool = pool_for(klass)
602
- klass = klass.superclass
603
- break unless klass <= Base
604
- 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)
605
1150
 
606
- class_to_pool[klass.name] = pool
1151
+ if pool_config
1152
+ pool_config.disconnect!
1153
+ pool_config.db_config
1154
+ end
607
1155
  end
608
1156
  end
609
1157
 
610
- private
611
-
612
- def owner_to_pool
613
- @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
614
1164
  end
615
1165
 
616
- def class_to_pool
617
- @class_to_pool[Process.pid]
618
- end
1166
+ private
1167
+ attr_reader :owner_to_pool_manager
619
1168
 
620
- def pool_for(owner)
621
- owner_to_pool.fetch(owner.name) {
622
- if ancestor_pool = pool_from_any_process_for(owner)
623
- # A connection was established in an ancestor process that must have
624
- # subsequently forked. We can't reuse the connection, but we can copy
625
- # the specification and establish a new connection with it.
626
- establish_connection owner, ancestor_pool.spec
627
- else
628
- 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 7.0.0. Please use `ActiveRecord::Base`.")
1181
+ owner_to_pool_manager[Base.name]
629
1182
  end
630
- }
631
- end
632
-
633
- def pool_from_any_process_for(owner)
634
- owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] }
635
- owner_to_pool && owner_to_pool[owner.name]
636
- end
637
- end
1183
+ end
638
1184
 
639
- class ConnectionManagement
640
- def initialize(app)
641
- @app = app
642
- 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
643
1220
 
644
- def call(env)
645
- 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
646
1224
 
647
- response = @app.call(env)
648
- response[2] = ::Rack::BodyProxy.new(response[2]) do
649
- ActiveRecord::Base.clear_active_connections! unless testing
1225
+ ConnectionAdapters::PoolConfig.new(owner_name, db_config)
650
1226
  end
651
-
652
- response
653
- rescue Exception
654
- ActiveRecord::Base.clear_active_connections! unless testing
655
- raise
656
- end
657
1227
  end
658
1228
  end
659
1229
  end