activerecord 4.2.0 → 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 (372) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +612 -971
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +13 -12
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +267 -248
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +135 -56
  11. data/lib/active_record/associations/association_scope.rb +103 -131
  12. data/lib/active_record/associations/belongs_to_association.rb +67 -54
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
  14. data/lib/active_record/associations/builder/association.rb +27 -40
  15. data/lib/active_record/associations/builder/belongs_to.rb +69 -55
  16. data/lib/active_record/associations/builder/collection_association.rb +10 -29
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +60 -70
  18. data/lib/active_record/associations/builder/has_many.rb +8 -4
  19. data/lib/active_record/associations/builder/has_one.rb +46 -5
  20. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  21. data/lib/active_record/associations/collection_association.rb +138 -274
  22. data/lib/active_record/associations/collection_proxy.rb +252 -151
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -83
  25. data/lib/active_record/associations/has_many_through_association.rb +62 -80
  26. data/lib/active_record/associations/has_one_association.rb +62 -49
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +38 -80
  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 +138 -162
  32. data/lib/active_record/associations/preloader/association.rb +90 -119
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +92 -94
  35. data/lib/active_record/associations/singular_association.rb +18 -45
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1737 -1596
  38. data/lib/active_record/attribute_assignment.rb +56 -183
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +15 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +174 -134
  42. data/lib/active_record/attribute_methods/primary_key.rb +91 -83
  43. data/lib/active_record/attribute_methods/query.rb +6 -5
  44. data/lib/active_record/attribute_methods/read.rb +20 -76
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +33 -55
  48. data/lib/active_record/attribute_methods.rb +124 -143
  49. data/lib/active_record/attributes.rb +214 -74
  50. data/lib/active_record/autosave_association.rb +115 -46
  51. data/lib/active_record/base.rb +60 -49
  52. data/lib/active_record/callbacks.rb +100 -74
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +796 -290
  56. data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
  57. data/lib/active_record/connection_adapters/abstract/database_statements.rb +247 -108
  58. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -23
  59. data/lib/active_record/connection_adapters/abstract/quoting.rb +171 -53
  60. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  61. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -46
  62. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +366 -227
  63. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  64. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +706 -222
  65. data/lib/active_record/connection_adapters/abstract/transaction.rb +191 -87
  66. data/lib/active_record/connection_adapters/abstract_adapter.rb +468 -194
  67. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +535 -597
  68. data/lib/active_record/connection_adapters/column.rb +56 -43
  69. data/lib/active_record/connection_adapters/connection_specification.rb +174 -152
  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 +59 -195
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +65 -115
  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 +50 -57
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
  91. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  94. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  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 +7 -9
  98. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +67 -51
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +10 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +474 -286
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -363
  116. data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
  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 +288 -359
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +176 -41
  128. data/lib/active_record/core.rb +266 -233
  129. data/lib/active_record/counter_cache.rb +68 -50
  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 +87 -105
  136. data/lib/active_record/enum.rb +164 -88
  137. data/lib/active_record/errors.rb +189 -53
  138. data/lib/active_record/explain.rb +23 -11
  139. data/lib/active_record/explain_registry.rb +4 -2
  140. data/lib/active_record/explain_subscriber.rb +11 -6
  141. data/lib/active_record/fixture_set/file.rb +35 -9
  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 +226 -495
  147. data/lib/active_record/gem_version.rb +4 -2
  148. data/lib/active_record/inheritance.rb +158 -112
  149. data/lib/active_record/insert_all.rb +179 -0
  150. data/lib/active_record/integration.rb +123 -29
  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 +3 -2
  154. data/lib/active_record/locking/optimistic.rb +91 -98
  155. data/lib/active_record/locking/pessimistic.rb +18 -6
  156. data/lib/active_record/log_subscriber.rb +76 -33
  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 +177 -90
  161. data/lib/active_record/migration/compatibility.rb +244 -0
  162. data/lib/active_record/migration/join_table.rb +8 -6
  163. data/lib/active_record/migration.rb +634 -288
  164. data/lib/active_record/model_schema.rb +314 -112
  165. data/lib/active_record/nested_attributes.rb +266 -214
  166. data/lib/active_record/no_touching.rb +15 -2
  167. data/lib/active_record/null_relation.rb +24 -37
  168. data/lib/active_record/persistence.rb +559 -124
  169. data/lib/active_record/query_cache.rb +19 -23
  170. data/lib/active_record/querying.rb +43 -29
  171. data/lib/active_record/railtie.rb +148 -47
  172. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  173. data/lib/active_record/railties/console_sandbox.rb +2 -0
  174. data/lib/active_record/railties/controller_runtime.rb +34 -33
  175. data/lib/active_record/railties/databases.rake +338 -202
  176. data/lib/active_record/readonly_attributes.rb +5 -4
  177. data/lib/active_record/reflection.rb +460 -299
  178. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  179. data/lib/active_record/relation/batches.rb +207 -55
  180. data/lib/active_record/relation/calculations.rb +269 -248
  181. data/lib/active_record/relation/delegation.rb +70 -80
  182. data/lib/active_record/relation/finder_methods.rb +279 -255
  183. data/lib/active_record/relation/from_clause.rb +26 -0
  184. data/lib/active_record/relation/merger.rb +83 -69
  185. data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -25
  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 +7 -1
  192. data/lib/active_record/relation/predicate_builder.rb +116 -92
  193. data/lib/active_record/relation/query_attribute.rb +50 -0
  194. data/lib/active_record/relation/query_methods.rb +574 -391
  195. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  196. data/lib/active_record/relation/spawn_methods.rb +18 -16
  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 +518 -340
  200. data/lib/active_record/result.rb +79 -42
  201. data/lib/active_record/runtime_registry.rb +6 -4
  202. data/lib/active_record/sanitization.rb +144 -121
  203. data/lib/active_record/schema.rb +21 -24
  204. data/lib/active_record/schema_dumper.rb +112 -93
  205. data/lib/active_record/schema_migration.rb +24 -20
  206. data/lib/active_record/scoping/default.rb +101 -84
  207. data/lib/active_record/scoping/named.rb +86 -33
  208. data/lib/active_record/scoping.rb +45 -26
  209. data/lib/active_record/secure_token.rb +40 -0
  210. data/lib/active_record/serialization.rb +5 -5
  211. data/lib/active_record/statement_cache.rb +73 -36
  212. data/lib/active_record/store.rb +127 -42
  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 +309 -99
  216. data/lib/active_record/tasks/mysql_database_tasks.rb +58 -88
  217. data/lib/active_record/tasks/postgresql_database_tasks.rb +82 -31
  218. data/lib/active_record/tasks/sqlite_database_tasks.rb +38 -16
  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 +86 -40
  222. data/lib/active_record/touch_later.rb +66 -0
  223. data/lib/active_record/transactions.rb +215 -139
  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 +4 -41
  227. data/lib/active_record/type/date_time.rb +4 -38
  228. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  229. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  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 +30 -15
  233. data/lib/active_record/type/text.rb +2 -2
  234. data/lib/active_record/type/time.rb +11 -16
  235. data/lib/active_record/type/type_map.rb +15 -17
  236. data/lib/active_record/type/unsigned_integer.rb +9 -7
  237. data/lib/active_record/type.rb +78 -23
  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 +13 -4
  243. data/lib/active_record/validations/length.rb +26 -0
  244. data/lib/active_record/validations/presence.rb +14 -13
  245. data/lib/active_record/validations/uniqueness.rb +43 -46
  246. data/lib/active_record/validations.rb +39 -35
  247. data/lib/active_record/version.rb +3 -1
  248. data/lib/active_record.rb +43 -21
  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 +42 -37
  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 → migration.rb.tt} +11 -8
  335. data/lib/rails/generators/active_record/migration.rb +31 -1
  336. data/lib/rails/generators/active_record/model/model_generator.rb +19 -22
  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 +166 -60
  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_set/builder.rb +0 -86
  349. data/lib/active_record/attribute_set.rb +0 -77
  350. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  351. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  352. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  353. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  354. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  355. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  356. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  357. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  358. data/lib/active_record/type/big_integer.rb +0 -13
  359. data/lib/active_record/type/binary.rb +0 -50
  360. data/lib/active_record/type/boolean.rb +0 -30
  361. data/lib/active_record/type/decimal.rb +0 -40
  362. data/lib/active_record/type/decorator.rb +0 -14
  363. data/lib/active_record/type/float.rb +0 -19
  364. data/lib/active_record/type/integer.rb +0 -55
  365. data/lib/active_record/type/mutable.rb +0 -16
  366. data/lib/active_record/type/numeric.rb +0 -36
  367. data/lib/active_record/type/string.rb +0 -36
  368. data/lib/active_record/type/time_value.rb +0 -38
  369. data/lib/active_record/type/value.rb +0 -101
  370. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -22
  371. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
  372. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,8 +1,9 @@
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
2
+
3
+ require "thread"
4
+ require "concurrent/map"
5
+ require "monitor"
6
+ require "weakref"
6
7
 
7
8
  module ActiveRecord
8
9
  # Raised when a connection could not be obtained within the connection
@@ -11,7 +12,34 @@ module ActiveRecord
11
12
  class ConnectionTimeoutError < ConnectionNotEstablished
12
13
  end
13
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
+
14
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
+
15
43
  # Connection pool base class for managing Active Record database
16
44
  # connections.
17
45
  #
@@ -33,17 +61,18 @@ module ActiveRecord
33
61
  # Connections can be obtained and used from a connection pool in several
34
62
  # ways:
35
63
  #
36
- # 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
37
66
  # earlier (pre-connection-pooling). Eventually, when you're done with
38
67
  # 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
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
41
70
  # Action Pack's request handling cycle.
42
71
  # 2. Manually check out a connection from the pool with
43
- # ActiveRecord::Base.connection_pool.checkout. You are responsible for
72
+ # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
44
73
  # 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
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
47
76
  # obtains a connection, yields it as the sole argument to the block,
48
77
  # and returns it to the pool after the block completes.
49
78
  #
@@ -55,21 +84,25 @@ module ActiveRecord
55
84
  # There are several connection-pooling-related options that you can add to
56
85
  # your database connection configuration:
57
86
  #
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).
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
67
103
  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.
104
+ # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
105
+ # with which it shares a Monitor.
73
106
  class Queue
74
107
  def initialize(lock = Monitor.new)
75
108
  @lock = lock
@@ -101,7 +134,7 @@ module ActiveRecord
101
134
  end
102
135
  end
103
136
 
104
- # If +element+ is in the queue, remove and return it, or nil.
137
+ # If +element+ is in the queue, remove and return it, or +nil+.
105
138
  def delete(element)
106
139
  synchronize do
107
140
  @queue.delete(element)
@@ -120,86 +153,160 @@ module ActiveRecord
120
153
  # If +timeout+ is not given, remove and return the head the
121
154
  # queue if the number of available elements is strictly
122
155
  # greater than the number of threads currently waiting (that
123
- # is, don't jump ahead in line). Otherwise, return nil.
156
+ # is, don't jump ahead in line). Otherwise, return +nil+.
124
157
  #
125
- # If +timeout+ is given, block if it there is no element
158
+ # If +timeout+ is given, block if there is no element
126
159
  # available, waiting up to +timeout+ seconds for an element to
127
160
  # become available.
128
161
  #
129
162
  # Raises:
130
- # - ConnectionTimeoutError if +timeout+ is given and no element
131
- # becomes available after +timeout+ seconds,
163
+ # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
164
+ # becomes available within +timeout+ seconds,
132
165
  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
166
+ synchronize { internal_poll(timeout) }
140
167
  end
141
168
 
142
169
  private
143
170
 
144
- def synchronize(&block)
145
- @lock.synchronize(&block)
146
- end
171
+ def internal_poll(timeout)
172
+ no_wait_poll || (timeout && wait_poll(timeout))
173
+ end
147
174
 
148
- # Test if the queue currently contains any elements.
149
- def any?
150
- !@queue.empty?
151
- end
175
+ def synchronize(&block)
176
+ @lock.synchronize(&block)
177
+ end
152
178
 
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
179
+ # Test if the queue currently contains any elements.
180
+ def any?
181
+ !@queue.empty?
182
+ end
160
183
 
161
- # Removes and returns the head of the queue if possible, or nil.
162
- def remove
163
- @queue.shift
164
- end
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
165
191
 
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
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
172
229
 
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
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
177
242
 
178
- t0 = Time.now
179
- elapsed = 0
180
- loop do
181
- @cond.wait(timeout - elapsed)
243
+ def broadcast
244
+ broadcast_on_biased
245
+ @other_cond.broadcast
246
+ end
182
247
 
183
- return remove if any?
248
+ def broadcast_on_biased
249
+ @num_waiting_on_real_cond = 0
250
+ @real_cond.broadcast
251
+ end
184
252
 
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
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)
191
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
192
280
  ensure
193
- @num_waiting -= 1
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
194
285
  end
195
286
  end
196
287
 
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.
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.
200
307
  #
201
- # Configure the frequency by setting "reaping_frequency" in your
202
- # database yaml file.
308
+ # Configure the frequency by setting +reaping_frequency+ in your database
309
+ # yaml file (default 60 seconds).
203
310
  class Reaper
204
311
  attr_reader :pool, :frequency
205
312
 
@@ -208,21 +315,51 @@ module ActiveRecord
208
315
  @frequency = frequency
209
316
  end
210
317
 
211
- def run
212
- return unless frequency
213
- Thread.new(frequency, pool) { |t, p|
214
- while true
215
- sleep t
216
- p.reap
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
217
348
  end
218
- }
349
+ end
350
+
351
+ def run
352
+ return unless frequency && frequency > 0
353
+ self.class.register_pool(pool, frequency)
219
354
  end
220
355
  end
221
356
 
222
357
  include MonitorMixin
358
+ include QueryCache::ConnectionPoolConfiguration
359
+ include ConnectionAdapters::AbstractPool
223
360
 
224
- attr_accessor :automatic_reconnect, :checkout_timeout
225
- attr_reader :spec, :connections, :size, :reaper
361
+ attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
362
+ attr_reader :spec, :size, :reaper
226
363
 
227
364
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
228
365
  # object which describes database connection information (e.g. adapter,
@@ -236,62 +373,98 @@ module ActiveRecord
236
373
  @spec = spec
237
374
 
238
375
  @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
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
241
380
 
242
381
  # default max pool size to 5
243
382
  @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
244
383
 
245
- # The cache of reserved connections mapped to threads
246
- @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
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)
247
395
 
248
396
  @connections = []
249
397
  @automatic_reconnect = true
250
398
 
251
- @available = Queue.new self
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
252
423
  end
253
424
 
254
425
  # Retrieve the connection associated with the current thread, or call
255
426
  # #checkout to obtain one if necessary.
256
427
  #
257
428
  # #connection can be called any number of times; the connection is
258
- # held in a hash keyed by the thread id.
429
+ # held in a cache keyed by a thread.
259
430
  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
431
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
265
432
  end
266
433
 
267
- # 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?
268
439
  def active_connection?
269
- synchronize do
270
- @reserved_connections.fetch(current_connection_id) {
271
- return false
272
- }.in_use?
273
- end
440
+ @thread_cached_conns[connection_cache_key(current_thread)]
274
441
  end
275
442
 
276
443
  # Signal that the thread is finished with the current connection.
277
444
  # #release_connection releases the connection-thread association
278
445
  # 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
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
283
453
  end
284
454
  end
285
455
 
286
- # 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
287
458
  # exists checkout a connection, yield it to the block, and checkin the
288
459
  # connection when finished.
289
460
  def with_connection
290
- connection_id = current_connection_id
291
- fresh_connection = true unless active_connection?
292
- 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
293
466
  ensure
294
- release_connection(connection_id) if fresh_connection
467
+ release_connection if fresh_connection
295
468
  end
296
469
 
297
470
  # Returns true if a connection has already been opened.
@@ -299,37 +472,103 @@ module ActiveRecord
299
472
  synchronize { @connections.any? }
300
473
  end
301
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
+
302
490
  # 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!
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
309
508
  end
310
- @connections = []
311
- @available.clear
312
509
  end
313
510
  end
314
511
 
315
- # Clears the cache which maps classes.
316
- 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:
317
528
  synchronize do
318
- @reserved_connections.clear
529
+ return if @connections.nil? # already discarded
319
530
  @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?
531
+ conn.discard!
325
532
  end
326
- @available.clear
327
- @connections.each do |conn|
328
- @available.add conn
533
+ @connections = @available = @thread_cached_conns = nil
534
+ end
535
+ end
536
+
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
329
556
  end
330
557
  end
331
558
  end
332
559
 
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)
570
+ end
571
+
333
572
  # Check-out a database connection from the pool, indicating that you want
334
573
  # to use it. You should call #checkin when you no longer need this.
335
574
  #
@@ -343,123 +582,361 @@ module ActiveRecord
343
582
  # Returns: an AbstractAdapter object.
344
583
  #
345
584
  # 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
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))
353
588
  end
354
589
 
355
590
  # Check-in a database connection back into the pool, indicating that you
356
591
  # no longer need this connection.
357
592
  #
358
593
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
359
- # calling +checkout+ on this pool.
594
+ # calling #checkout on this pool.
360
595
  def checkin(conn)
361
- synchronize do
362
- owner = conn.owner
363
-
364
- conn._run_checkin_callbacks do
365
- conn.expire
366
- end
596
+ conn.lock.synchronize do
597
+ synchronize do
598
+ remove_connection_from_thread_cache conn
367
599
 
368
- release owner
600
+ conn._run_checkin_callbacks do
601
+ conn.expire
602
+ end
369
603
 
370
- @available.add conn
604
+ @available.add conn
605
+ end
371
606
  end
372
607
  end
373
608
 
374
- # Remove a connection from the connection pool. The connection will
609
+ # Remove a connection from the connection pool. The connection will
375
610
  # remain open and active but will no longer be managed by this pool.
376
611
  def remove(conn)
612
+ needs_new_connection = false
613
+
377
614
  synchronize do
615
+ remove_connection_from_thread_cache conn
616
+
378
617
  @connections.delete conn
379
618
  @available.delete conn
380
619
 
381
- release conn.owner
382
-
383
- @available.add checkout_new_connection if @available.any_waiting?
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?
384
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
385
638
  end
386
639
 
387
- # Recover lost connections for the pool. A lost connection can occur if
640
+ # Recover lost connections for the pool. A lost connection can occur if
388
641
  # a programmer forgets to checkin a connection at the end of a thread
389
642
  # or a thread dies unexpectedly.
390
643
  def reap
391
644
  stale_connections = synchronize do
392
645
  @connections.select do |conn|
393
646
  conn.in_use? && !conn.owner.alive?
647
+ end.each do |conn|
648
+ conn.steal!
394
649
  end
395
650
  end
396
651
 
397
652
  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
653
+ if conn.active?
654
+ conn.reset!
655
+ checkin conn
656
+ else
657
+ remove conn
405
658
  end
406
659
  end
407
660
  end
408
661
 
409
- private
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?
410
667
 
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)
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
426
677
  end
427
- end
428
678
 
429
- def release(owner)
430
- thread_id = owner.object_id
679
+ idle_connections.each do |conn|
680
+ conn.disconnect!
681
+ end
682
+ end
431
683
 
432
- @reserved_connections.delete thread_id
684
+ # Disconnect all currently idle connections. Connections currently checked
685
+ # out are unaffected.
686
+ def flush!
687
+ reap
688
+ flush(-1)
433
689
  end
434
690
 
435
- def new_connection
436
- Base.send(spec.adapter_method, spec.config)
691
+ def num_waiting_in_queue # :nodoc:
692
+ @available.num_waiting
437
693
  end
438
694
 
439
- def current_connection_id #:nodoc:
440
- Base.connection_id ||= Thread.current.object_id
695
+ # Return connection pool's usage statistic
696
+ # Example:
697
+ #
698
+ # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
699
+ def stat
700
+ synchronize do
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
441
711
  end
442
712
 
443
- def checkout_new_connection
444
- raise ConnectionNotEstablished unless @automatic_reconnect
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)
722
+ end
723
+ end
724
+ end
445
725
 
446
- c = new_connection
447
- c.pool = self
448
- @connections << c
449
- c
450
- 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
451
734
 
452
- def checkout_and_verify(c)
453
- c._run_checkout_callbacks do
454
- c.verify!
735
+ def current_thread
736
+ @lock_thread || Thread.current
737
+ end
738
+
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
769
+ end
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
792
+ end
793
+
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
809
+ end
810
+
811
+ msg << " (#{thread_report.join(', ')})" if thread_report.any?
812
+
813
+ raise ExclusiveConnectionTimeoutError, msg
814
+ end
815
+
816
+ def with_new_connections_blocked
817
+ synchronize do
818
+ @threads_blocking_new_connections += 1
819
+ end
820
+
821
+ yield
822
+ ensure
823
+ num_new_conns_required = 0
824
+
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
840
+ end
841
+
842
+ bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
843
+ end
844
+
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
870
+
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
883
+
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
915
+
916
+ def adopt_connection(conn)
917
+ conn.pool = self
918
+ @connections << conn
919
+ end
920
+
921
+ def checkout_new_connection
922
+ raise ConnectionNotEstablished unless @automatic_reconnect
923
+ new_connection
924
+ end
925
+
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
455
935
  end
456
- c
457
- end
458
936
  end
459
937
 
460
938
  # 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.
939
+ # for keeping separate connection pools that connect to different databases.
463
940
  #
464
941
  # For example, suppose that you have 5 models, with the following hierarchy:
465
942
  #
@@ -470,7 +947,7 @@ module ActiveRecord
470
947
  # end
471
948
  #
472
949
  # class Book < ActiveRecord::Base
473
- # establish_connection "library_db"
950
+ # establish_connection :library_db
474
951
  # end
475
952
  #
476
953
  # class ScaryBook < Book
@@ -501,36 +978,87 @@ module ActiveRecord
501
978
  # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
502
979
  # All Active Record models use this handler to determine the connection pool that they
503
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.
504
985
  class ConnectionHandler
505
- 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)
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
995
+
996
+ def self.unowned_pool_finalizer(pid_map) # :nodoc:
997
+ lambda do |_|
998
+ discard_unowned_pools(pid_map)
511
999
  end
512
- @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
513
- h[k] = ThreadSafe::Cache.new
1000
+ end
1001
+
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
514
1005
  end
515
1006
  end
516
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
+
517
1037
  def connection_pool_list
518
1038
  owner_to_pool.values.compact
519
1039
  end
1040
+ alias :connection_pools :connection_pool_list
520
1041
 
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
1042
+ def establish_connection(config)
1043
+ resolver = ConnectionSpecification::Resolver.new(Base.configurations)
1044
+ spec = resolver.spec(config)
526
1045
 
527
- Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
528
- end
1046
+ remove_connection(spec.name)
529
1047
 
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)
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]
534
1062
  end
535
1063
 
536
1064
  # Returns true if there are any active connections among the connection
@@ -547,6 +1075,8 @@ module ActiveRecord
547
1075
  end
548
1076
 
549
1077
  # Clears the cache which maps classes.
1078
+ #
1079
+ # See ConnectionPool#clear_reloadable_connections! for details.
550
1080
  def clear_reloadable_connections!
551
1081
  connection_pool_list.each(&:clear_reloadable_connections!)
552
1082
  end
@@ -555,105 +1085,81 @@ module ActiveRecord
555
1085
  connection_pool_list.each(&:disconnect!)
556
1086
  end
557
1087
 
1088
+ # Disconnects all currently idle connections.
1089
+ #
1090
+ # See ConnectionPool#flush! for details.
1091
+ def flush_idle_connections!
1092
+ connection_pool_list.each(&:flush!)
1093
+ end
1094
+
558
1095
  # Locate the connection of the nearest super class. This can be an
559
1096
  # active or defined connection: if it is the latter, it will be
560
1097
  # opened and set as the active connection for the class it was defined
561
1098
  # 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
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
568
1112
  end
569
1113
 
570
1114
  # Returns true if a connection that's accessible to this class has
571
1115
  # already been opened.
572
- def connected?(klass)
573
- conn = retrieve_connection_pool(klass)
574
- conn && conn.connected?
1116
+ def connected?(spec_name)
1117
+ pool = retrieve_connection_pool(spec_name)
1118
+ pool && pool.connected?
575
1119
  end
576
1120
 
577
1121
  # Remove the connection for this class. This will close the active
578
1122
  # connection and the defined connection (if they exist). The result
579
- # can be used as an argument for establish_connection, for easily
1123
+ # can be used as an argument for #establish_connection, for easily
580
1124
  # re-establishing the connection.
581
- def remove_connection(owner)
582
- if pool = owner_to_pool.delete(owner.name)
583
- @class_to_pool.clear
1125
+ def remove_connection(spec_name)
1126
+ if pool = owner_to_pool.delete(spec_name)
584
1127
  pool.automatic_reconnect = false
585
1128
  pool.disconnect!
586
1129
  pool.spec.config
587
1130
  end
588
1131
  end
589
1132
 
590
- # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
1133
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
591
1134
  # This makes retrieving the connection pool O(1) once the process is warm.
592
1135
  # 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
605
-
606
- class_to_pool[klass.name] = pool
607
- end
608
- end
609
-
610
- private
611
-
612
- def owner_to_pool
613
- @owner_to_pool[Process.pid]
614
- end
615
-
616
- def class_to_pool
617
- @class_to_pool[Process.pid]
618
- end
619
-
620
- def pool_for(owner)
621
- owner_to_pool.fetch(owner.name) {
622
- if ancestor_pool = pool_from_any_process_for(owner)
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)
623
1141
  # A connection was established in an ancestor process that must have
624
1142
  # subsequently forked. We can't reuse the connection, but we can copy
625
1143
  # the specification and establish a new connection with it.
626
- establish_connection owner, ancestor_pool.spec
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
627
1147
  else
628
- owner_to_pool[owner.name] = nil
1148
+ owner_to_pool[spec_name] = nil
629
1149
  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
638
-
639
- class ConnectionManagement
640
- def initialize(app)
641
- @app = app
1150
+ end
642
1151
  end
643
1152
 
644
- def call(env)
645
- testing = env['rack.test']
1153
+ private
646
1154
 
647
- response = @app.call(env)
648
- response[2] = ::Rack::BodyProxy.new(response[2]) do
649
- ActiveRecord::Base.clear_active_connections! unless testing
1155
+ def owner_to_pool
1156
+ @owner_to_pool[Process.pid]
650
1157
  end
651
1158
 
652
- response
653
- rescue Exception
654
- ActiveRecord::Base.clear_active_connections! unless testing
655
- raise
656
- end
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]
1162
+ end
657
1163
  end
658
1164
  end
659
1165
  end