activerecord 4.2.11.1 → 6.0.3.5

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