activerecord 3.2.6 → 6.0.0

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

Potentially problematic release.


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

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