activerecord 4.2.0 → 6.1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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