activerecord 5.0.7.2 → 6.1.1

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 (363) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +829 -2015
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +11 -9
  5. data/examples/performance.rb +31 -29
  6. data/examples/simple.rb +5 -3
  7. data/lib/active_record.rb +37 -29
  8. data/lib/active_record/aggregations.rb +249 -247
  9. data/lib/active_record/association_relation.rb +30 -18
  10. data/lib/active_record/associations.rb +1714 -1596
  11. data/lib/active_record/associations/alias_tracker.rb +36 -42
  12. data/lib/active_record/associations/association.rb +143 -68
  13. data/lib/active_record/associations/association_scope.rb +98 -94
  14. data/lib/active_record/associations/belongs_to_association.rb +76 -46
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  16. data/lib/active_record/associations/builder/association.rb +27 -28
  17. data/lib/active_record/associations/builder/belongs_to.rb +52 -60
  18. data/lib/active_record/associations/builder/collection_association.rb +12 -22
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +40 -62
  20. data/lib/active_record/associations/builder/has_many.rb +10 -2
  21. data/lib/active_record/associations/builder/has_one.rb +35 -2
  22. data/lib/active_record/associations/builder/singular_association.rb +5 -1
  23. data/lib/active_record/associations/collection_association.rb +104 -259
  24. data/lib/active_record/associations/collection_proxy.rb +169 -125
  25. data/lib/active_record/associations/foreign_association.rb +22 -0
  26. data/lib/active_record/associations/has_many_association.rb +46 -31
  27. data/lib/active_record/associations/has_many_through_association.rb +66 -46
  28. data/lib/active_record/associations/has_one_association.rb +71 -52
  29. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  30. data/lib/active_record/associations/join_dependency.rb +169 -180
  31. data/lib/active_record/associations/join_dependency/join_association.rb +53 -79
  32. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  33. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  34. data/lib/active_record/associations/preloader.rb +97 -104
  35. data/lib/active_record/associations/preloader/association.rb +109 -97
  36. data/lib/active_record/associations/preloader/through_association.rb +77 -76
  37. data/lib/active_record/associations/singular_association.rb +12 -45
  38. data/lib/active_record/associations/through_association.rb +27 -15
  39. data/lib/active_record/attribute_assignment.rb +55 -60
  40. data/lib/active_record/attribute_methods.rb +111 -141
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -9
  42. data/lib/active_record/attribute_methods/dirty.rb +172 -112
  43. data/lib/active_record/attribute_methods/primary_key.rb +88 -91
  44. data/lib/active_record/attribute_methods/query.rb +6 -8
  45. data/lib/active_record/attribute_methods/read.rb +18 -50
  46. data/lib/active_record/attribute_methods/serialization.rb +38 -10
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -66
  48. data/lib/active_record/attribute_methods/write.rb +25 -32
  49. data/lib/active_record/attributes.rb +69 -31
  50. data/lib/active_record/autosave_association.rb +102 -66
  51. data/lib/active_record/base.rb +16 -25
  52. data/lib/active_record/callbacks.rb +202 -43
  53. data/lib/active_record/coders/json.rb +2 -0
  54. data/lib/active_record/coders/yaml_column.rb +11 -12
  55. data/lib/active_record/connection_adapters.rb +50 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +661 -375
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +14 -38
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +269 -105
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +54 -35
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +137 -93
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +155 -113
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -162
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +591 -259
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +229 -91
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +392 -244
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +457 -582
  69. data/lib/active_record/connection_adapters/column.rb +55 -13
  70. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  71. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +8 -31
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +135 -49
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +24 -23
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -20
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +79 -49
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +66 -56
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +20 -12
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +74 -37
  82. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  83. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/column.rb +39 -28
  85. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -101
  86. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
  87. data/lib/active_record/connection_adapters/postgresql/oid.rb +26 -21
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +22 -11
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +6 -5
  90. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -6
  93. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -4
  95. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +19 -18
  98. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/{json.rb → oid.rb} +6 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +30 -9
  106. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -30
  107. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  109. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  110. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +98 -38
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +21 -27
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +147 -105
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +34 -32
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +426 -324
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +32 -23
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +418 -293
  121. data/lib/active_record/connection_adapters/schema_cache.rb +135 -18
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +22 -7
  123. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  124. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +3 -1
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +72 -18
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -6
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +282 -290
  131. data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
  132. data/lib/active_record/connection_handling.rb +287 -45
  133. data/lib/active_record/core.rb +385 -181
  134. data/lib/active_record/counter_cache.rb +60 -28
  135. data/lib/active_record/database_configurations.rb +272 -0
  136. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  137. data/lib/active_record/database_configurations/database_config.rb +80 -0
  138. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  139. data/lib/active_record/database_configurations/url_config.rb +53 -0
  140. data/lib/active_record/delegated_type.rb +209 -0
  141. data/lib/active_record/destroy_association_async_job.rb +36 -0
  142. data/lib/active_record/dynamic_matchers.rb +87 -87
  143. data/lib/active_record/enum.rb +122 -47
  144. data/lib/active_record/errors.rb +153 -22
  145. data/lib/active_record/explain.rb +13 -8
  146. data/lib/active_record/explain_registry.rb +3 -1
  147. data/lib/active_record/explain_subscriber.rb +9 -4
  148. data/lib/active_record/fixture_set/file.rb +20 -22
  149. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  150. data/lib/active_record/fixture_set/render_context.rb +17 -0
  151. data/lib/active_record/fixture_set/table_row.rb +152 -0
  152. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  153. data/lib/active_record/fixtures.rb +246 -507
  154. data/lib/active_record/gem_version.rb +6 -4
  155. data/lib/active_record/inheritance.rb +168 -95
  156. data/lib/active_record/insert_all.rb +208 -0
  157. data/lib/active_record/integration.rb +114 -25
  158. data/lib/active_record/internal_metadata.rb +30 -24
  159. data/lib/active_record/legacy_yaml_adapter.rb +11 -5
  160. data/lib/active_record/locking/optimistic.rb +81 -85
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +68 -31
  163. data/lib/active_record/middleware/database_selector.rb +77 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  166. data/lib/active_record/migration.rb +439 -342
  167. data/lib/active_record/migration/command_recorder.rb +152 -98
  168. data/lib/active_record/migration/compatibility.rb +229 -60
  169. data/lib/active_record/migration/join_table.rb +8 -7
  170. data/lib/active_record/model_schema.rb +230 -122
  171. data/lib/active_record/nested_attributes.rb +213 -203
  172. data/lib/active_record/no_touching.rb +11 -2
  173. data/lib/active_record/null_relation.rb +12 -34
  174. data/lib/active_record/persistence.rb +471 -97
  175. data/lib/active_record/query_cache.rb +23 -12
  176. data/lib/active_record/querying.rb +43 -25
  177. data/lib/active_record/railtie.rb +155 -43
  178. data/lib/active_record/railties/console_sandbox.rb +2 -0
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +507 -195
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +245 -269
  183. data/lib/active_record/relation.rb +475 -324
  184. data/lib/active_record/relation/batches.rb +125 -72
  185. data/lib/active_record/relation/batches/batch_enumerator.rb +28 -10
  186. data/lib/active_record/relation/calculations.rb +267 -171
  187. data/lib/active_record/relation/delegation.rb +73 -69
  188. data/lib/active_record/relation/finder_methods.rb +238 -248
  189. data/lib/active_record/relation/from_clause.rb +7 -9
  190. data/lib/active_record/relation/merger.rb +95 -77
  191. data/lib/active_record/relation/predicate_builder.rb +109 -110
  192. data/lib/active_record/relation/predicate_builder/array_handler.rb +22 -17
  193. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  194. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
  195. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +55 -0
  196. data/lib/active_record/relation/predicate_builder/range_handler.rb +7 -18
  197. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  198. data/lib/active_record/relation/query_attribute.rb +33 -2
  199. data/lib/active_record/relation/query_methods.rb +654 -374
  200. data/lib/active_record/relation/record_fetch_warning.rb +8 -6
  201. data/lib/active_record/relation/spawn_methods.rb +15 -14
  202. data/lib/active_record/relation/where_clause.rb +171 -109
  203. data/lib/active_record/result.rb +88 -51
  204. data/lib/active_record/runtime_registry.rb +5 -3
  205. data/lib/active_record/sanitization.rb +73 -100
  206. data/lib/active_record/schema.rb +7 -14
  207. data/lib/active_record/schema_dumper.rb +101 -69
  208. data/lib/active_record/schema_migration.rb +16 -12
  209. data/lib/active_record/scoping.rb +20 -20
  210. data/lib/active_record/scoping/default.rb +92 -95
  211. data/lib/active_record/scoping/named.rb +39 -30
  212. data/lib/active_record/secure_token.rb +19 -9
  213. data/lib/active_record/serialization.rb +7 -3
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +80 -29
  216. data/lib/active_record/store.rb +122 -42
  217. data/lib/active_record/suppressor.rb +6 -3
  218. data/lib/active_record/table_metadata.rb +51 -39
  219. data/lib/active_record/tasks/database_tasks.rb +332 -115
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +66 -104
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -56
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +40 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +246 -0
  225. data/lib/active_record/timestamp.rb +70 -38
  226. data/lib/active_record/touch_later.rb +26 -24
  227. data/lib/active_record/transactions.rb +121 -184
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type.rb +29 -17
  230. data/lib/active_record/type/adapter_specific_registry.rb +44 -48
  231. data/lib/active_record/type/date.rb +2 -0
  232. data/lib/active_record/type/date_time.rb +2 -0
  233. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  234. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  235. data/lib/active_record/type/internal/timezone.rb +2 -0
  236. data/lib/active_record/type/json.rb +30 -0
  237. data/lib/active_record/type/serialized.rb +20 -9
  238. data/lib/active_record/type/text.rb +11 -0
  239. data/lib/active_record/type/time.rb +12 -1
  240. data/lib/active_record/type/type_map.rb +14 -17
  241. data/lib/active_record/type/unsigned_integer.rb +16 -0
  242. data/lib/active_record/type_caster.rb +4 -2
  243. data/lib/active_record/type_caster/connection.rb +17 -13
  244. data/lib/active_record/type_caster/map.rb +10 -6
  245. data/lib/active_record/validations.rb +8 -5
  246. data/lib/active_record/validations/absence.rb +2 -0
  247. data/lib/active_record/validations/associated.rb +4 -3
  248. data/lib/active_record/validations/length.rb +2 -0
  249. data/lib/active_record/validations/numericality.rb +35 -0
  250. data/lib/active_record/validations/presence.rb +4 -2
  251. data/lib/active_record/validations/uniqueness.rb +52 -45
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/arel.rb +54 -0
  254. data/lib/arel/alias_predication.rb +9 -0
  255. data/lib/arel/attributes/attribute.rb +41 -0
  256. data/lib/arel/collectors/bind.rb +29 -0
  257. data/lib/arel/collectors/composite.rb +39 -0
  258. data/lib/arel/collectors/plain_string.rb +20 -0
  259. data/lib/arel/collectors/sql_string.rb +27 -0
  260. data/lib/arel/collectors/substitute_binds.rb +35 -0
  261. data/lib/arel/crud.rb +42 -0
  262. data/lib/arel/delete_manager.rb +18 -0
  263. data/lib/arel/errors.rb +9 -0
  264. data/lib/arel/expressions.rb +29 -0
  265. data/lib/arel/factory_methods.rb +49 -0
  266. data/lib/arel/insert_manager.rb +49 -0
  267. data/lib/arel/math.rb +45 -0
  268. data/lib/arel/nodes.rb +70 -0
  269. data/lib/arel/nodes/and.rb +32 -0
  270. data/lib/arel/nodes/ascending.rb +23 -0
  271. data/lib/arel/nodes/binary.rb +126 -0
  272. data/lib/arel/nodes/bind_param.rb +44 -0
  273. data/lib/arel/nodes/case.rb +55 -0
  274. data/lib/arel/nodes/casted.rb +62 -0
  275. data/lib/arel/nodes/comment.rb +29 -0
  276. data/lib/arel/nodes/count.rb +12 -0
  277. data/lib/arel/nodes/delete_statement.rb +45 -0
  278. data/lib/arel/nodes/descending.rb +23 -0
  279. data/lib/arel/nodes/equality.rb +15 -0
  280. data/lib/arel/nodes/extract.rb +24 -0
  281. data/lib/arel/nodes/false.rb +16 -0
  282. data/lib/arel/nodes/full_outer_join.rb +8 -0
  283. data/lib/arel/nodes/function.rb +44 -0
  284. data/lib/arel/nodes/grouping.rb +11 -0
  285. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  286. data/lib/arel/nodes/in.rb +15 -0
  287. data/lib/arel/nodes/infix_operation.rb +92 -0
  288. data/lib/arel/nodes/inner_join.rb +8 -0
  289. data/lib/arel/nodes/insert_statement.rb +37 -0
  290. data/lib/arel/nodes/join_source.rb +20 -0
  291. data/lib/arel/nodes/matches.rb +18 -0
  292. data/lib/arel/nodes/named_function.rb +23 -0
  293. data/lib/arel/nodes/node.rb +51 -0
  294. data/lib/arel/nodes/node_expression.rb +13 -0
  295. data/lib/arel/nodes/ordering.rb +27 -0
  296. data/lib/arel/nodes/outer_join.rb +8 -0
  297. data/lib/arel/nodes/over.rb +15 -0
  298. data/lib/arel/nodes/regexp.rb +16 -0
  299. data/lib/arel/nodes/right_outer_join.rb +8 -0
  300. data/lib/arel/nodes/select_core.rb +67 -0
  301. data/lib/arel/nodes/select_statement.rb +41 -0
  302. data/lib/arel/nodes/sql_literal.rb +19 -0
  303. data/lib/arel/nodes/string_join.rb +11 -0
  304. data/lib/arel/nodes/table_alias.rb +31 -0
  305. data/lib/arel/nodes/terminal.rb +16 -0
  306. data/lib/arel/nodes/true.rb +16 -0
  307. data/lib/arel/nodes/unary.rb +44 -0
  308. data/lib/arel/nodes/unary_operation.rb +20 -0
  309. data/lib/arel/nodes/unqualified_column.rb +22 -0
  310. data/lib/arel/nodes/update_statement.rb +41 -0
  311. data/lib/arel/nodes/values_list.rb +9 -0
  312. data/lib/arel/nodes/window.rb +126 -0
  313. data/lib/arel/nodes/with.rb +11 -0
  314. data/lib/arel/order_predications.rb +13 -0
  315. data/lib/arel/predications.rb +250 -0
  316. data/lib/arel/select_manager.rb +270 -0
  317. data/lib/arel/table.rb +118 -0
  318. data/lib/arel/tree_manager.rb +72 -0
  319. data/lib/arel/update_manager.rb +34 -0
  320. data/lib/arel/visitors.rb +13 -0
  321. data/lib/arel/visitors/dot.rb +308 -0
  322. data/lib/arel/visitors/mysql.rb +93 -0
  323. data/lib/arel/visitors/postgresql.rb +120 -0
  324. data/lib/arel/visitors/sqlite.rb +38 -0
  325. data/lib/arel/visitors/to_sql.rb +899 -0
  326. data/lib/arel/visitors/visitor.rb +45 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/rails/generators/active_record.rb +7 -5
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  331. data/lib/rails/generators/active_record/migration.rb +22 -3
  332. data/lib/rails/generators/active_record/migration/migration_generator.rb +38 -35
  333. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +3 -1
  334. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +7 -5
  335. data/lib/rails/generators/active_record/model/model_generator.rb +41 -25
  336. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  337. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +10 -1
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. metadata +141 -57
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  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 -15
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -20
  347. data/lib/active_record/attribute.rb +0 -213
  348. data/lib/active_record/attribute/user_provided_default.rb +0 -28
  349. data/lib/active_record/attribute_decorators.rb +0 -67
  350. data/lib/active_record/attribute_mutation_tracker.rb +0 -70
  351. data/lib/active_record/attribute_set.rb +0 -110
  352. data/lib/active_record/attribute_set/builder.rb +0 -132
  353. data/lib/active_record/collection_cache_key.rb +0 -50
  354. data/lib/active_record/connection_adapters/connection_specification.rb +0 -263
  355. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -22
  356. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +0 -50
  357. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  358. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  359. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -17
  360. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
  361. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
  362. data/lib/active_record/relation/where_clause_factory.rb +0 -38
  363. data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Coders # :nodoc:
3
5
  class JSON # :nodoc:
@@ -1,12 +1,14 @@
1
- require 'yaml'
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
2
4
 
3
5
  module ActiveRecord
4
6
  module Coders # :nodoc:
5
7
  class YAMLColumn # :nodoc:
6
-
7
8
  attr_accessor :object_class
8
9
 
9
- def initialize(object_class = Object)
10
+ def initialize(attr_name, object_class = Object)
11
+ @attr_name = attr_name
10
12
  @object_class = object_class
11
13
  check_arity_of_constructor
12
14
  end
@@ -14,37 +16,34 @@ module ActiveRecord
14
16
  def dump(obj)
15
17
  return if obj.nil?
16
18
 
17
- assert_valid_value(obj)
19
+ assert_valid_value(obj, action: "dump")
18
20
  YAML.dump obj
19
21
  end
20
22
 
21
23
  def load(yaml)
22
24
  return object_class.new if object_class != Object && yaml.nil?
23
- return yaml unless yaml.is_a?(String) && yaml =~ /^---/
25
+ return yaml unless yaml.is_a?(String) && yaml.start_with?("---")
24
26
  obj = YAML.load(yaml)
25
27
 
26
- assert_valid_value(obj)
28
+ assert_valid_value(obj, action: "load")
27
29
  obj ||= object_class.new if object_class != Object
28
30
 
29
31
  obj
30
32
  end
31
33
 
32
- def assert_valid_value(obj)
34
+ def assert_valid_value(obj, action:)
33
35
  unless obj.nil? || obj.is_a?(object_class)
34
36
  raise SerializationTypeMismatch,
35
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
37
+ "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
36
38
  end
37
39
  end
38
40
 
39
41
  private
40
-
41
- def check_arity_of_constructor
42
- begin
42
+ def check_arity_of_constructor
43
43
  load(nil)
44
44
  rescue ArgumentError
45
45
  raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
46
46
  end
47
- end
48
47
  end
49
48
  end
50
49
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ extend ActiveSupport::Autoload
6
+
7
+ eager_autoload do
8
+ autoload :AbstractAdapter
9
+ end
10
+
11
+ autoload :Column
12
+ autoload :PoolConfig
13
+ autoload :PoolManager
14
+ autoload :LegacyPoolManager
15
+
16
+ autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
17
+ autoload :IndexDefinition
18
+ autoload :ColumnDefinition
19
+ autoload :ChangeColumnDefinition
20
+ autoload :ForeignKeyDefinition
21
+ autoload :CheckConstraintDefinition
22
+ autoload :TableDefinition
23
+ autoload :Table
24
+ autoload :AlterTable
25
+ autoload :ReferenceDefinition
26
+ end
27
+
28
+ autoload_at "active_record/connection_adapters/abstract/connection_pool" do
29
+ autoload :ConnectionHandler
30
+ end
31
+
32
+ autoload_under "abstract" do
33
+ autoload :SchemaStatements
34
+ autoload :DatabaseStatements
35
+ autoload :DatabaseLimits
36
+ autoload :Quoting
37
+ autoload :ConnectionPool
38
+ autoload :QueryCache
39
+ autoload :Savepoints
40
+ end
41
+
42
+ autoload_at "active_record/connection_adapters/abstract/transaction" do
43
+ autoload :TransactionManager
44
+ autoload :NullTransaction
45
+ autoload :RealTransaction
46
+ autoload :SavepointTransaction
47
+ autoload :TransactionState
48
+ end
49
+ end
50
+ end
@@ -1,22 +1,34 @@
1
- require 'thread'
2
- require 'concurrent/map'
3
- require 'monitor'
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "concurrent/map"
5
+ require "monitor"
6
+ require "weakref"
4
7
 
5
8
  module ActiveRecord
6
- # Raised when a connection could not be obtained within the connection
7
- # acquisition timeout period: because max connections in pool
8
- # are in use.
9
- class ConnectionTimeoutError < ConnectionNotEstablished
10
- end
9
+ module ConnectionAdapters
10
+ module AbstractPool # :nodoc:
11
+ def get_schema_cache(connection)
12
+ self.schema_cache ||= SchemaCache.new(connection)
13
+ schema_cache.connection = connection
14
+ schema_cache
15
+ end
11
16
 
12
- # Raised when a pool was unable to get ahold of all its connections
13
- # to perform a "group" action such as
14
- # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
15
- # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
16
- class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
17
- end
17
+ def set_schema_cache(cache)
18
+ self.schema_cache = cache
19
+ end
20
+ end
21
+
22
+ class NullPool # :nodoc:
23
+ include ConnectionAdapters::AbstractPool
24
+
25
+ attr_accessor :schema_cache
26
+
27
+ def owner_name
28
+ nil
29
+ end
30
+ end
18
31
 
19
- module ConnectionAdapters
20
32
  # Connection pool base class for managing Active Record database
21
33
  # connections.
22
34
  #
@@ -61,30 +73,25 @@ module ActiveRecord
61
73
  # There are several connection-pooling-related options that you can add to
62
74
  # your database connection configuration:
63
75
  #
64
- # * +pool+: number indicating size of connection pool (default 5)
65
- # * +checkout_timeout+: number of seconds to block and wait for a connection
66
- # before giving up and raising a timeout error (default 5 seconds).
67
- # * +reaping_frequency+: frequency in seconds to periodically run the
68
- # Reaper, which attempts to find and recover connections from dead
69
- # threads, which can occur if a programmer forgets to close a
70
- # connection at the end of a thread or a thread dies unexpectedly.
71
- # Regardless of this setting, the Reaper will be invoked before every
72
- # blocking wait. (Default nil, which means don't schedule the Reaper).
76
+ # * +pool+: maximum number of connections the pool may manage (default 5).
77
+ # * +idle_timeout+: number of seconds that a connection will be kept
78
+ # unused in the pool before it is automatically disconnected (default
79
+ # 300 seconds). Set this to zero to keep connections forever.
80
+ # * +checkout_timeout+: number of seconds to wait for a connection to
81
+ # become available before giving up and raising a timeout error (default
82
+ # 5 seconds).
73
83
  #
74
84
  #--
75
85
  # Synchronization policy:
76
86
  # * all public methods can be called outside +synchronize+
77
- # * access to these i-vars needs to be in +synchronize+:
87
+ # * access to these instance variables needs to be in +synchronize+:
78
88
  # * @connections
79
89
  # * @now_connecting
80
90
  # * private methods that require being called in a +synchronize+ blocks
81
91
  # are now explicitly documented
82
92
  class ConnectionPool
83
- # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
84
- # with which it shares a Monitor. But could be a generic Queue.
85
- #
86
- # The Queue in stdlib's 'thread' could replace this class except
87
- # stdlib's doesn't support waiting with a timeout.
93
+ # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
94
+ # with which it shares a Monitor.
88
95
  class Queue
89
96
  def initialize(lock = Monitor.new)
90
97
  @lock = lock
@@ -116,7 +123,7 @@ module ActiveRecord
116
123
  end
117
124
  end
118
125
 
119
- # If +element+ is in the queue, remove and return it, or nil.
126
+ # If +element+ is in the queue, remove and return it, or +nil+.
120
127
  def delete(element)
121
128
  synchronize do
122
129
  @queue.delete(element)
@@ -132,10 +139,10 @@ module ActiveRecord
132
139
 
133
140
  # Remove the head of the queue.
134
141
  #
135
- # If +timeout+ is not given, remove and return the head the
142
+ # If +timeout+ is not given, remove and return the head of the
136
143
  # queue if the number of available elements is strictly
137
144
  # greater than the number of threads currently waiting (that
138
- # is, don't jump ahead in line). Otherwise, return nil.
145
+ # is, don't jump ahead in line). Otherwise, return +nil+.
139
146
  #
140
147
  # If +timeout+ is given, block if there is no element
141
148
  # available, waiting up to +timeout+ seconds for an element to
@@ -149,62 +156,63 @@ module ActiveRecord
149
156
  end
150
157
 
151
158
  private
159
+ def internal_poll(timeout)
160
+ no_wait_poll || (timeout && wait_poll(timeout))
161
+ end
152
162
 
153
- def internal_poll(timeout)
154
- no_wait_poll || (timeout && wait_poll(timeout))
155
- end
156
-
157
- def synchronize(&block)
158
- @lock.synchronize(&block)
159
- end
163
+ def synchronize(&block)
164
+ @lock.synchronize(&block)
165
+ end
160
166
 
161
- # Test if the queue currently contains any elements.
162
- def any?
163
- !@queue.empty?
164
- end
167
+ # Test if the queue currently contains any elements.
168
+ def any?
169
+ !@queue.empty?
170
+ end
165
171
 
166
- # A thread can remove an element from the queue without
167
- # waiting if and only if the number of currently available
168
- # connections is strictly greater than the number of waiting
169
- # threads.
170
- def can_remove_no_wait?
171
- @queue.size > @num_waiting
172
- end
172
+ # A thread can remove an element from the queue without
173
+ # waiting if and only if the number of currently available
174
+ # connections is strictly greater than the number of waiting
175
+ # threads.
176
+ def can_remove_no_wait?
177
+ @queue.size > @num_waiting
178
+ end
173
179
 
174
- # Removes and returns the head of the queue if possible, or nil.
175
- def remove
176
- @queue.shift
177
- end
180
+ # Removes and returns the head of the queue if possible, or +nil+.
181
+ def remove
182
+ @queue.pop
183
+ end
178
184
 
179
- # Remove and return the head the queue if the number of
180
- # available elements is strictly greater than the number of
181
- # threads currently waiting. Otherwise, return nil.
182
- def no_wait_poll
183
- remove if can_remove_no_wait?
184
- end
185
+ # Remove and return the head of the queue if the number of
186
+ # available elements is strictly greater than the number of
187
+ # threads currently waiting. Otherwise, return +nil+.
188
+ def no_wait_poll
189
+ remove if can_remove_no_wait?
190
+ end
185
191
 
186
- # Waits on the queue up to +timeout+ seconds, then removes and
187
- # returns the head of the queue.
188
- def wait_poll(timeout)
189
- @num_waiting += 1
192
+ # Waits on the queue up to +timeout+ seconds, then removes and
193
+ # returns the head of the queue.
194
+ def wait_poll(timeout)
195
+ @num_waiting += 1
190
196
 
191
- t0 = Time.now
192
- elapsed = 0
193
- loop do
194
- @cond.wait(timeout - elapsed)
197
+ t0 = Concurrent.monotonic_time
198
+ elapsed = 0
199
+ loop do
200
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
201
+ @cond.wait(timeout - elapsed)
202
+ end
195
203
 
196
- return remove if any?
204
+ return remove if any?
197
205
 
198
- elapsed = Time.now - t0
199
- if elapsed >= timeout
200
- msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' %
201
- [timeout, elapsed]
202
- raise ConnectionTimeoutError, msg
206
+ elapsed = Concurrent.monotonic_time - t0
207
+ if elapsed >= timeout
208
+ msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
209
+ [timeout, elapsed]
210
+ raise ConnectionTimeoutError, msg
211
+ end
203
212
  end
213
+ ensure
214
+ @num_waiting -= 1
204
215
  end
205
- ensure
206
- @num_waiting -= 1
207
- end
208
216
  end
209
217
 
210
218
  # Adds the ability to turn a basic fair FIFO queue into one
@@ -268,25 +276,25 @@ module ActiveRecord
268
276
  # Connections must be leased while holding the main pool mutex. This is
269
277
  # an internal subclass that also +.leases+ returned connections while
270
278
  # still in queue's critical section (queue synchronizes with the same
271
- # +@lock+ as the main pool) so that a returned connection is already
279
+ # <tt>@lock</tt> as the main pool) so that a returned connection is already
272
280
  # leased and there is no need to re-enter synchronized block.
273
281
  class ConnectionLeasingQueue < Queue # :nodoc:
274
282
  include BiasableQueue
275
283
 
276
284
  private
277
- def internal_poll(timeout)
278
- conn = super
279
- conn.lease if conn
280
- conn
281
- end
285
+ def internal_poll(timeout)
286
+ conn = super
287
+ conn.lease if conn
288
+ conn
289
+ end
282
290
  end
283
291
 
284
- # Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
285
- # A reaper instantiated with a nil frequency will never reap the
286
- # connection pool.
292
+ # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
293
+ # +pool+. A reaper instantiated with a zero frequency will never reap
294
+ # the connection pool.
287
295
  #
288
- # Configure the frequency by setting "reaping_frequency" in your
289
- # database yaml file.
296
+ # Configure the frequency by setting +reaping_frequency+ in your database
297
+ # yaml file (default 60 seconds).
290
298
  class Reaper
291
299
  attr_reader :pool, :frequency
292
300
 
@@ -295,52 +303,95 @@ module ActiveRecord
295
303
  @frequency = frequency
296
304
  end
297
305
 
298
- def run
299
- return unless frequency
300
- Thread.new(frequency, pool) { |t, p|
301
- while true
302
- sleep t
303
- p.reap
306
+ @mutex = Mutex.new
307
+ @pools = {}
308
+ @threads = {}
309
+
310
+ class << self
311
+ def register_pool(pool, frequency) # :nodoc:
312
+ @mutex.synchronize do
313
+ unless @threads[frequency]&.alive?
314
+ @threads[frequency] = spawn_thread(frequency)
315
+ end
316
+ @pools[frequency] ||= []
317
+ @pools[frequency] << WeakRef.new(pool)
318
+ end
319
+ end
320
+
321
+ private
322
+ def spawn_thread(frequency)
323
+ Thread.new(frequency) do |t|
324
+ # Advise multi-threaded app servers to ignore this thread for
325
+ # the purposes of fork safety warnings
326
+ Thread.current.thread_variable_set(:fork_safe, true)
327
+ running = true
328
+ while running
329
+ sleep t
330
+ @mutex.synchronize do
331
+ @pools[frequency].select! do |pool|
332
+ pool.weakref_alive? && !pool.discarded?
333
+ end
334
+
335
+ @pools[frequency].each do |p|
336
+ p.reap
337
+ p.flush
338
+ rescue WeakRef::RefError
339
+ end
340
+
341
+ if @pools[frequency].empty?
342
+ @pools.delete(frequency)
343
+ @threads.delete(frequency)
344
+ running = false
345
+ end
346
+ end
347
+ end
348
+ end
304
349
  end
305
- }
350
+ end
351
+
352
+ def run
353
+ return unless frequency && frequency > 0
354
+ self.class.register_pool(pool, frequency)
306
355
  end
307
356
  end
308
357
 
309
358
  include MonitorMixin
310
359
  include QueryCache::ConnectionPoolConfiguration
360
+ include ConnectionAdapters::AbstractPool
361
+
362
+ attr_accessor :automatic_reconnect, :checkout_timeout
363
+ attr_reader :db_config, :size, :reaper, :pool_config, :owner_name
311
364
 
312
- attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
313
- attr_reader :spec, :connections, :size, :reaper
365
+ delegate :schema_cache, :schema_cache=, to: :pool_config
314
366
 
315
- # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
367
+ # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
316
368
  # object which describes database connection information (e.g. adapter,
317
369
  # host name, username, password, etc), as well as the maximum size for
318
370
  # this ConnectionPool.
319
371
  #
320
372
  # The default ConnectionPool maximum size is 5.
321
- def initialize(spec)
373
+ def initialize(pool_config)
322
374
  super()
323
375
 
324
- @spec = spec
376
+ @pool_config = pool_config
377
+ @db_config = pool_config.db_config
378
+ @owner_name = pool_config.connection_specification_name
325
379
 
326
- @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
327
- @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
328
- @reaper.run
329
-
330
- # default max pool size to 5
331
- @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
380
+ @checkout_timeout = db_config.checkout_timeout
381
+ @idle_timeout = db_config.idle_timeout
382
+ @size = db_config.pool
332
383
 
333
- # The cache of threads mapped to reserved connections, the sole purpose
334
- # of the cache is to speed-up +connection+ method, it is not the authoritative
335
- # registry of which thread owns which connection, that is tracked by
336
- # +connection.owner+ attr on each +connection+ instance.
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.
337
388
  # The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
338
- # then that +thread+ does indeed own that +conn+, however an absence of a such
339
- # mapping does not mean that the +thread+ doesn't own the said connection, in
389
+ # then that +thread+ does indeed own that +conn+. However, an absence of such
390
+ # mapping does not mean that the +thread+ doesn't own the said connection. In
340
391
  # that case +conn.owner+ attr should be consulted.
341
- # Access and modification of +@thread_cached_conns+ does not require
392
+ # Access and modification of <tt>@thread_cached_conns</tt> does not require
342
393
  # synchronization.
343
- @thread_cached_conns = Concurrent::Map.new(:initial_capacity => @size)
394
+ @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
344
395
 
345
396
  @connections = []
346
397
  @automatic_reconnect = true
@@ -353,6 +404,19 @@ module ActiveRecord
353
404
  @threads_blocking_new_connections = 0
354
405
 
355
406
  @available = ConnectionLeasingQueue.new self
407
+
408
+ @lock_thread = false
409
+
410
+ @reaper = Reaper.new(self, db_config.reaping_frequency)
411
+ @reaper.run
412
+ end
413
+
414
+ def lock_thread=(lock_thread)
415
+ if lock_thread
416
+ @lock_thread = Thread.current
417
+ else
418
+ @lock_thread = nil
419
+ end
356
420
  end
357
421
 
358
422
  # Retrieve the connection associated with the current thread, or call
@@ -361,16 +425,16 @@ module ActiveRecord
361
425
  # #connection can be called any number of times; the connection is
362
426
  # held in a cache keyed by a thread.
363
427
  def connection
364
- @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
428
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
365
429
  end
366
430
 
367
- # Is there an open connection that is being used for the current thread?
431
+ # Returns true if there is an open connection being used for the current thread.
368
432
  #
369
433
  # This method only works for connections that have been obtained through
370
- # #connection or #with_connection methods, connections obtained through
434
+ # #connection or #with_connection methods. Connections obtained through
371
435
  # #checkout will not be detected by #active_connection?
372
436
  def active_connection?
373
- @thread_cached_conns[connection_cache_key(Thread.current)]
437
+ @thread_cached_conns[connection_cache_key(current_thread)]
374
438
  end
375
439
 
376
440
  # Signal that the thread is finished with the current connection.
@@ -405,12 +469,27 @@ module ActiveRecord
405
469
  synchronize { @connections.any? }
406
470
  end
407
471
 
472
+ # Returns an array containing the connections currently in the pool.
473
+ # Access to the array does not require synchronization on the pool because
474
+ # the array is newly created and not retained by the pool.
475
+ #
476
+ # However; this method bypasses the ConnectionPool's thread-safe connection
477
+ # access pattern. A returned connection may be owned by another thread,
478
+ # unowned, or by happen-stance owned by the calling thread.
479
+ #
480
+ # Calling methods on a connection without ownership is subject to the
481
+ # thread-safety guarantees of the underlying method. Many of the methods
482
+ # on connection adapter classes are inherently multi-thread unsafe.
483
+ def connections
484
+ synchronize { @connections.dup }
485
+ end
486
+
408
487
  # Disconnects all connections in the pool, and clears the pool.
409
488
  #
410
489
  # Raises:
411
490
  # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
412
491
  # connections in the pool within a timeout interval (default duration is
413
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
492
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
414
493
  def disconnect(raise_on_acquisition_timeout = true)
415
494
  with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
416
495
  synchronize do
@@ -429,21 +508,40 @@ module ActiveRecord
429
508
 
430
509
  # Disconnects all connections in the pool, and clears the pool.
431
510
  #
432
- # The pool first tries to gain ownership of all connections, if unable to
511
+ # The pool first tries to gain ownership of all connections. If unable to
433
512
  # do so within a timeout interval (default duration is
434
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool is forcefully
513
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool is forcefully
435
514
  # disconnected without any regard for other connection owning threads.
436
515
  def disconnect!
437
516
  disconnect(false)
438
517
  end
439
518
 
519
+ # Discards all connections in the pool (even if they're currently
520
+ # leased!), along with the pool itself. Any further interaction with the
521
+ # pool (except #spec and #schema_cache) is undefined.
522
+ #
523
+ # See AbstractAdapter#discard!
524
+ def discard! # :nodoc:
525
+ synchronize do
526
+ return if self.discarded?
527
+ @connections.each do |conn|
528
+ conn.discard!
529
+ end
530
+ @connections = @available = @thread_cached_conns = nil
531
+ end
532
+ end
533
+
534
+ def discarded? # :nodoc:
535
+ @connections.nil?
536
+ end
537
+
440
538
  # Clears the cache which maps classes and re-connects connections that
441
539
  # require reloading.
442
540
  #
443
541
  # Raises:
444
542
  # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
445
543
  # connections in the pool within a timeout interval (default duration is
446
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
544
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
447
545
  def clear_reloadable_connections(raise_on_acquisition_timeout = true)
448
546
  with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
449
547
  synchronize do
@@ -463,9 +561,9 @@ module ActiveRecord
463
561
  # Clears the cache which maps classes and re-connects connections that
464
562
  # require reloading.
465
563
  #
466
- # The pool first tries to gain ownership of all connections, if unable to
564
+ # The pool first tries to gain ownership of all connections. If unable to
467
565
  # do so within a timeout interval (default duration is
468
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool forcefully
566
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool forcefully
469
567
  # clears the cache and reloads connections without any regard for other
470
568
  # connection owning threads.
471
569
  def clear_reloadable_connections!
@@ -496,14 +594,16 @@ module ActiveRecord
496
594
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
497
595
  # calling #checkout on this pool.
498
596
  def checkin(conn)
499
- synchronize do
500
- remove_connection_from_thread_cache conn
597
+ conn.lock.synchronize do
598
+ synchronize do
599
+ remove_connection_from_thread_cache conn
501
600
 
502
- conn._run_checkin_callbacks do
503
- conn.expire
504
- end
601
+ conn._run_checkin_callbacks do
602
+ conn.expire
603
+ end
505
604
 
506
- @available.add conn
605
+ @available.add conn
606
+ end
507
607
  end
508
608
  end
509
609
 
@@ -519,20 +619,20 @@ module ActiveRecord
519
619
  @available.delete conn
520
620
 
521
621
  # @available.any_waiting? => true means that prior to removing this
522
- # conn, the pool was at its max size (@connections.size == @size)
523
- # this would mean that any threads stuck waiting in the queue wouldn't
622
+ # conn, the pool was at its max size (@connections.size == @size).
623
+ # This would mean that any threads stuck waiting in the queue wouldn't
524
624
  # know they could checkout_new_connection, so let's do it for them.
525
625
  # Because condition-wait loop is encapsulated in the Queue class
526
626
  # (that in turn is oblivious to ConnectionPool implementation), threads
527
- # that are "stuck" there are helpless, they have no way of creating
627
+ # that are "stuck" there are helpless. They have no way of creating
528
628
  # new connections and are completely reliant on us feeding available
529
629
  # connections into the Queue.
530
630
  needs_new_connection = @available.any_waiting?
531
631
  end
532
632
 
533
633
  # This is intentionally done outside of the synchronized section as we
534
- # would like not to hold the main mutex while checking out new connections,
535
- # thus there is some chance that needs_new_connection information is now
634
+ # would like not to hold the main mutex while checking out new connections.
635
+ # Thus there is some chance that needs_new_connection information is now
536
636
  # stale, we can live with that (bulk_make_new_connections will make
537
637
  # sure not to exceed the pool's @size limit).
538
638
  bulk_make_new_connections(1) if needs_new_connection
@@ -543,6 +643,7 @@ module ActiveRecord
543
643
  # or a thread dies unexpectedly.
544
644
  def reap
545
645
  stale_connections = synchronize do
646
+ return if self.discarded?
546
647
  @connections.select do |conn|
547
648
  conn.in_use? && !conn.owner.alive?
548
649
  end.each do |conn|
@@ -560,229 +661,281 @@ module ActiveRecord
560
661
  end
561
662
  end
562
663
 
664
+ # Disconnect all connections that have been idle for at least
665
+ # +minimum_idle+ seconds. Connections currently checked out, or that were
666
+ # checked in less than +minimum_idle+ seconds ago, are unaffected.
667
+ def flush(minimum_idle = @idle_timeout)
668
+ return if minimum_idle.nil?
669
+
670
+ idle_connections = synchronize do
671
+ return if self.discarded?
672
+ @connections.select do |conn|
673
+ !conn.in_use? && conn.seconds_idle >= minimum_idle
674
+ end.each do |conn|
675
+ conn.lease
676
+
677
+ @available.delete conn
678
+ @connections.delete conn
679
+ end
680
+ end
681
+
682
+ idle_connections.each do |conn|
683
+ conn.disconnect!
684
+ end
685
+ end
686
+
687
+ # Disconnect all currently idle connections. Connections currently checked
688
+ # out are unaffected.
689
+ def flush!
690
+ reap
691
+ flush(-1)
692
+ end
693
+
563
694
  def num_waiting_in_queue # :nodoc:
564
695
  @available.num_waiting
565
696
  end
566
697
 
567
- private
568
- #--
569
- # this is unfortunately not concurrent
570
- def bulk_make_new_connections(num_new_conns_needed)
571
- num_new_conns_needed.times do
572
- # try_to_checkout_new_connection will not exceed pool's @size limit
573
- if new_conn = try_to_checkout_new_connection
574
- # make the new_conn available to the starving threads stuck @available Queue
575
- checkin(new_conn)
576
- end
577
- end
578
- end
579
-
580
- #--
581
- # From the discussion on GitHub:
582
- # https://github.com/rails/rails/pull/14938#commitcomment-6601951
583
- # This hook-in method allows for easier monkey-patching fixes needed by
584
- # JRuby users that use Fibers.
585
- def connection_cache_key(thread)
586
- thread
587
- end
588
-
589
- # Take control of all existing connections so a "group" action such as
590
- # reload/disconnect can be performed safely. It is no longer enough to
591
- # wrap it in +synchronize+ because some pool's actions are allowed
592
- # to be performed outside of the main +synchronize+ block.
593
- def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
594
- with_new_connections_blocked do
595
- attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
596
- yield
698
+ # Return connection pool's usage statistic
699
+ # Example:
700
+ #
701
+ # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
702
+ def stat
703
+ synchronize do
704
+ {
705
+ size: size,
706
+ connections: @connections.size,
707
+ busy: @connections.count { |c| c.in_use? && c.owner.alive? },
708
+ dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
709
+ idle: @connections.count { |c| !c.in_use? },
710
+ waiting: num_waiting_in_queue,
711
+ checkout_timeout: checkout_timeout
712
+ }
597
713
  end
598
714
  end
599
715
 
600
- def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
601
- collected_conns = synchronize do
602
- # account for our own connections
603
- @connections.select {|conn| conn.owner == Thread.current}
604
- end
605
-
606
- newly_checked_out = []
607
- timeout_time = Time.now + (@checkout_timeout * 2)
608
-
609
- @available.with_a_bias_for(Thread.current) do
610
- while true
611
- synchronize do
612
- return if collected_conns.size == @connections.size && @now_connecting == 0
613
- remaining_timeout = timeout_time - Time.now
614
- remaining_timeout = 0 if remaining_timeout < 0
615
- conn = checkout_for_exclusive_access(remaining_timeout)
616
- collected_conns << conn
617
- newly_checked_out << conn
716
+ private
717
+ #--
718
+ # this is unfortunately not concurrent
719
+ def bulk_make_new_connections(num_new_conns_needed)
720
+ num_new_conns_needed.times do
721
+ # try_to_checkout_new_connection will not exceed pool's @size limit
722
+ if new_conn = try_to_checkout_new_connection
723
+ # make the new_conn available to the starving threads stuck @available Queue
724
+ checkin(new_conn)
618
725
  end
619
726
  end
620
727
  end
621
- rescue ExclusiveConnectionTimeoutError
622
- # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
623
- # timeouts and are expected to just give up: we've obtained as many connections
624
- # as possible, note that in a case like that we don't return any of the
625
- # +newly_checked_out+ connections.
626
728
 
627
- if raise_on_acquisition_timeout
628
- release_newly_checked_out = true
629
- raise
729
+ #--
730
+ # From the discussion on GitHub:
731
+ # https://github.com/rails/rails/pull/14938#commitcomment-6601951
732
+ # This hook-in method allows for easier monkey-patching fixes needed by
733
+ # JRuby users that use Fibers.
734
+ def connection_cache_key(thread)
735
+ thread
630
736
  end
631
- rescue Exception # if something else went wrong
632
- # this can't be a "naked" rescue, because we have should return conns
633
- # even for non-StandardErrors
634
- release_newly_checked_out = true
635
- raise
636
- ensure
637
- if release_newly_checked_out && newly_checked_out
638
- # releasing only those conns that were checked out in this method, conns
639
- # checked outside this method (before it was called) are not for us to release
640
- newly_checked_out.each {|conn| checkin(conn)}
737
+
738
+ def current_thread
739
+ @lock_thread || Thread.current
641
740
  end
642
- end
643
741
 
644
- #--
645
- # Must be called in a synchronize block.
646
- def checkout_for_exclusive_access(checkout_timeout)
647
- checkout(checkout_timeout)
648
- rescue ConnectionTimeoutError
649
- # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
650
- # rescue block, because doing so would put it outside of synchronize section, without
651
- # being in a critical section thread_report might become inaccurate
652
- msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
742
+ # Take control of all existing connections so a "group" action such as
743
+ # reload/disconnect can be performed safely. It is no longer enough to
744
+ # wrap it in +synchronize+ because some pool's actions are allowed
745
+ # to be performed outside of the main +synchronize+ block.
746
+ def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
747
+ with_new_connections_blocked do
748
+ attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
749
+ yield
750
+ end
751
+ end
653
752
 
654
- thread_report = []
655
- @connections.each do |conn|
656
- unless conn.owner == Thread.current
657
- thread_report << "#{conn} is owned by #{conn.owner}"
753
+ def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
754
+ collected_conns = synchronize do
755
+ # account for our own connections
756
+ @connections.select { |conn| conn.owner == Thread.current }
757
+ end
758
+
759
+ newly_checked_out = []
760
+ timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
761
+
762
+ @available.with_a_bias_for(Thread.current) do
763
+ loop do
764
+ synchronize do
765
+ return if collected_conns.size == @connections.size && @now_connecting == 0
766
+ remaining_timeout = timeout_time - Concurrent.monotonic_time
767
+ remaining_timeout = 0 if remaining_timeout < 0
768
+ conn = checkout_for_exclusive_access(remaining_timeout)
769
+ collected_conns << conn
770
+ newly_checked_out << conn
771
+ end
772
+ end
773
+ end
774
+ rescue ExclusiveConnectionTimeoutError
775
+ # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
776
+ # timeouts and are expected to just give up: we've obtained as many connections
777
+ # as possible, note that in a case like that we don't return any of the
778
+ # +newly_checked_out+ connections.
779
+
780
+ if raise_on_acquisition_timeout
781
+ release_newly_checked_out = true
782
+ raise
783
+ end
784
+ rescue Exception # if something else went wrong
785
+ # this can't be a "naked" rescue, because we have should return conns
786
+ # even for non-StandardErrors
787
+ release_newly_checked_out = true
788
+ raise
789
+ ensure
790
+ if release_newly_checked_out && newly_checked_out
791
+ # releasing only those conns that were checked out in this method, conns
792
+ # checked outside this method (before it was called) are not for us to release
793
+ newly_checked_out.each { |conn| checkin(conn) }
658
794
  end
659
795
  end
660
796
 
661
- msg << " (#{thread_report.join(', ')})" if thread_report.any?
797
+ #--
798
+ # Must be called in a synchronize block.
799
+ def checkout_for_exclusive_access(checkout_timeout)
800
+ checkout(checkout_timeout)
801
+ rescue ConnectionTimeoutError
802
+ # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
803
+ # rescue block, because doing so would put it outside of synchronize section, without
804
+ # being in a critical section thread_report might become inaccurate
805
+ msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds"
806
+
807
+ thread_report = []
808
+ @connections.each do |conn|
809
+ unless conn.owner == Thread.current
810
+ thread_report << "#{conn} is owned by #{conn.owner}"
811
+ end
812
+ end
662
813
 
663
- raise ExclusiveConnectionTimeoutError, msg
664
- end
814
+ msg << " (#{thread_report.join(', ')})" if thread_report.any?
665
815
 
666
- def with_new_connections_blocked
667
- synchronize do
668
- @threads_blocking_new_connections += 1
816
+ raise ExclusiveConnectionTimeoutError, msg
669
817
  end
670
818
 
671
- yield
672
- ensure
673
- num_new_conns_required = 0
819
+ def with_new_connections_blocked
820
+ synchronize do
821
+ @threads_blocking_new_connections += 1
822
+ end
674
823
 
675
- synchronize do
676
- @threads_blocking_new_connections -= 1
824
+ yield
825
+ ensure
826
+ num_new_conns_required = 0
677
827
 
678
- if @threads_blocking_new_connections.zero?
679
- @available.clear
828
+ synchronize do
829
+ @threads_blocking_new_connections -= 1
680
830
 
681
- num_new_conns_required = num_waiting_in_queue
831
+ if @threads_blocking_new_connections.zero?
832
+ @available.clear
682
833
 
683
- @connections.each do |conn|
684
- next if conn.in_use?
834
+ num_new_conns_required = num_waiting_in_queue
685
835
 
686
- @available.add conn
687
- num_new_conns_required -= 1
836
+ @connections.each do |conn|
837
+ next if conn.in_use?
838
+
839
+ @available.add conn
840
+ num_new_conns_required -= 1
841
+ end
688
842
  end
689
843
  end
690
- end
691
844
 
692
- bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
693
- end
694
-
695
- # Acquire a connection by one of 1) immediately removing one
696
- # from the queue of available connections, 2) creating a new
697
- # connection if the pool is not at capacity, 3) waiting on the
698
- # queue for a connection to become available.
699
- #
700
- # Raises:
701
- # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
702
- #
703
- #--
704
- # Implementation detail: the connection returned by +acquire_connection+
705
- # will already be "+connection.lease+ -ed" to the current thread.
706
- def acquire_connection(checkout_timeout)
707
- # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
708
- # +conn.lease+ the returned connection (and to do this in a +synchronized+
709
- # section), this is not the cleanest implementation, as ideally we would
710
- # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
711
- # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
712
- # of the said methods and avoid an additional +synchronize+ overhead.
713
- if conn = @available.poll || try_to_checkout_new_connection
714
- conn
715
- else
716
- reap
717
- @available.poll(checkout_timeout)
845
+ bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
718
846
  end
719
- end
720
847
 
721
- #--
722
- # if owner_thread param is omitted, this must be called in synchronize block
723
- def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
724
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
725
- end
726
- alias_method :release, :remove_connection_from_thread_cache
848
+ # Acquire a connection by one of 1) immediately removing one
849
+ # from the queue of available connections, 2) creating a new
850
+ # connection if the pool is not at capacity, 3) waiting on the
851
+ # queue for a connection to become available.
852
+ #
853
+ # Raises:
854
+ # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
855
+ #
856
+ #--
857
+ # Implementation detail: the connection returned by +acquire_connection+
858
+ # will already be "+connection.lease+ -ed" to the current thread.
859
+ def acquire_connection(checkout_timeout)
860
+ # NOTE: we rely on <tt>@available.poll</tt> and +try_to_checkout_new_connection+ to
861
+ # +conn.lease+ the returned connection (and to do this in a +synchronized+
862
+ # section). This is not the cleanest implementation, as ideally we would
863
+ # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
864
+ # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
865
+ # of the said methods and avoid an additional +synchronize+ overhead.
866
+ if conn = @available.poll || try_to_checkout_new_connection
867
+ conn
868
+ else
869
+ reap
870
+ @available.poll(checkout_timeout)
871
+ end
872
+ end
727
873
 
728
- def new_connection
729
- Base.send(spec.adapter_method, spec.config).tap do |conn|
730
- conn.schema_cache = schema_cache.dup if schema_cache
874
+ #--
875
+ # if owner_thread param is omitted, this must be called in synchronize block
876
+ def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
877
+ @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
731
878
  end
732
- end
879
+ alias_method :release, :remove_connection_from_thread_cache
733
880
 
734
- # If the pool is not at a +@size+ limit, establish new connection. Connecting
735
- # to the DB is done outside main synchronized section.
736
- #--
737
- # Implementation constraint: a newly established connection returned by this
738
- # method must be in the +.leased+ state.
739
- def try_to_checkout_new_connection
740
- # first in synchronized section check if establishing new conns is allowed
741
- # and increment @now_connecting, to prevent overstepping this pool's @size
742
- # constraint
743
- do_checkout = synchronize do
744
- if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
745
- @now_connecting += 1
881
+ def new_connection
882
+ Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
883
+ conn.check_version
746
884
  end
747
885
  end
748
- if do_checkout
749
- begin
750
- # if successfully incremented @now_connecting establish new connection
751
- # outside of synchronized section
752
- conn = checkout_new_connection
753
- ensure
754
- synchronize do
755
- if conn
756
- adopt_connection(conn)
757
- # returned conn needs to be already leased
758
- conn.lease
886
+
887
+ # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
888
+ # to the DB is done outside main synchronized section.
889
+ #--
890
+ # Implementation constraint: a newly established connection returned by this
891
+ # method must be in the +.leased+ state.
892
+ def try_to_checkout_new_connection
893
+ # first in synchronized section check if establishing new conns is allowed
894
+ # and increment @now_connecting, to prevent overstepping this pool's @size
895
+ # constraint
896
+ do_checkout = synchronize do
897
+ if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
898
+ @now_connecting += 1
899
+ end
900
+ end
901
+ if do_checkout
902
+ begin
903
+ # if successfully incremented @now_connecting establish new connection
904
+ # outside of synchronized section
905
+ conn = checkout_new_connection
906
+ ensure
907
+ synchronize do
908
+ if conn
909
+ adopt_connection(conn)
910
+ # returned conn needs to be already leased
911
+ conn.lease
912
+ end
913
+ @now_connecting -= 1
759
914
  end
760
- @now_connecting -= 1
761
915
  end
762
916
  end
763
917
  end
764
- end
765
918
 
766
- def adopt_connection(conn)
767
- conn.pool = self
768
- @connections << conn
769
- end
919
+ def adopt_connection(conn)
920
+ conn.pool = self
921
+ @connections << conn
922
+ end
770
923
 
771
- def checkout_new_connection
772
- raise ConnectionNotEstablished unless @automatic_reconnect
773
- new_connection
774
- end
924
+ def checkout_new_connection
925
+ raise ConnectionNotEstablished unless @automatic_reconnect
926
+ new_connection
927
+ end
775
928
 
776
- def checkout_and_verify(c)
777
- c._run_checkout_callbacks do
778
- c.verify!
929
+ def checkout_and_verify(c)
930
+ c._run_checkout_callbacks do
931
+ c.verify!
932
+ end
933
+ c
934
+ rescue
935
+ remove c
936
+ c.disconnect!
937
+ raise
779
938
  end
780
- c
781
- rescue
782
- remove c
783
- c.disconnect!
784
- raise
785
- end
786
939
  end
787
940
 
788
941
  # ConnectionHandler is a collection of ConnectionPool objects. It is used
@@ -797,7 +950,7 @@ module ActiveRecord
797
950
  # end
798
951
  #
799
952
  # class Book < ActiveRecord::Base
800
- # establish_connection "library_db"
953
+ # establish_connection :library_db
801
954
  # end
802
955
  #
803
956
  # class ScaryBook < Book
@@ -829,115 +982,248 @@ module ActiveRecord
829
982
  # All Active Record models use this handler to determine the connection pool that they
830
983
  # should use.
831
984
  #
832
- # The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
833
- # about the model. The model, needs to pass a specification name to the handler,
834
- # in order to lookup the correct connection pool.
985
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
986
+ # about the model. The model needs to pass a connection specification name to the handler,
987
+ # in order to look up the correct connection pool.
835
988
  class ConnectionHandler
989
+ FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
990
+ private_constant :FINALIZER
991
+
836
992
  def initialize
837
- # These caches are keyed by spec.name (ConnectionSpecification#name).
838
- @owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
839
- h[k] = Concurrent::Map.new(:initial_capacity => 2)
993
+ # These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name).
994
+ @owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
995
+
996
+ # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
997
+ ObjectSpace.define_finalizer self, FINALIZER
998
+ end
999
+
1000
+ def prevent_writes # :nodoc:
1001
+ Thread.current[:prevent_writes]
1002
+ end
1003
+
1004
+ def prevent_writes=(prevent_writes) # :nodoc:
1005
+ Thread.current[:prevent_writes] = prevent_writes
1006
+ end
1007
+
1008
+ # Prevent writing to the database regardless of role.
1009
+ #
1010
+ # In some cases you may want to prevent writes to the database
1011
+ # even if you are on a database that can write. `while_preventing_writes`
1012
+ # will prevent writes to the database for the duration of the block.
1013
+ #
1014
+ # This method does not provide the same protection as a readonly
1015
+ # user and is meant to be a safeguard against accidental writes.
1016
+ #
1017
+ # See `READ_QUERY` for the queries that are blocked by this
1018
+ # method.
1019
+ def while_preventing_writes(enabled = true)
1020
+ unless ActiveRecord::Base.legacy_connection_handling
1021
+ raise NotImplementedError, "`while_preventing_writes` is only available on the connection_handler with legacy_connection_handling"
840
1022
  end
1023
+
1024
+ original, self.prevent_writes = self.prevent_writes, enabled
1025
+ yield
1026
+ ensure
1027
+ self.prevent_writes = original
1028
+ end
1029
+
1030
+ def connection_pool_names # :nodoc:
1031
+ owner_to_pool_manager.keys
1032
+ end
1033
+
1034
+ def all_connection_pools
1035
+ owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
841
1036
  end
842
1037
 
843
- def connection_pool_list
844
- owner_to_pool.values.compact
1038
+ def connection_pool_list(role = ActiveRecord::Base.current_role)
1039
+ owner_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
845
1040
  end
846
1041
  alias :connection_pools :connection_pool_list
847
1042
 
848
- def establish_connection(spec)
849
- owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
1043
+ def establish_connection(config, owner_name: Base.name, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
1044
+ owner_name = config.to_s if config.is_a?(Symbol)
1045
+
1046
+ pool_config = resolve_pool_config(config, owner_name)
1047
+ db_config = pool_config.db_config
1048
+
1049
+ # Protects the connection named `ActiveRecord::Base` from being removed
1050
+ # if the user calls `establish_connection :primary`.
1051
+ if owner_to_pool_manager.key?(pool_config.connection_specification_name)
1052
+ remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard)
1053
+ end
1054
+
1055
+ message_bus = ActiveSupport::Notifications.instrumenter
1056
+ payload = {}
1057
+ if pool_config
1058
+ payload[:spec_name] = pool_config.connection_specification_name
1059
+ payload[:shard] = shard
1060
+ payload[:config] = db_config.configuration_hash
1061
+ end
1062
+
1063
+ if ActiveRecord::Base.legacy_connection_handling
1064
+ owner_to_pool_manager[pool_config.connection_specification_name] ||= LegacyPoolManager.new
1065
+ else
1066
+ owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
1067
+ end
1068
+ pool_manager = get_pool_manager(pool_config.connection_specification_name)
1069
+ pool_manager.set_pool_config(role, shard, pool_config)
1070
+
1071
+ message_bus.instrument("!connection.active_record", payload) do
1072
+ pool_config.pool
1073
+ end
850
1074
  end
851
1075
 
852
1076
  # Returns true if there are any active connections among the connection
853
1077
  # pools that the ConnectionHandler is managing.
854
- def active_connections?
855
- connection_pool_list.any?(&:active_connection?)
1078
+ def active_connections?(role = ActiveRecord::Base.current_role)
1079
+ connection_pool_list(role).any?(&:active_connection?)
856
1080
  end
857
1081
 
858
1082
  # Returns any connections in use by the current thread back to the pool,
859
1083
  # and also returns connections to the pool cached by threads that are no
860
1084
  # longer alive.
861
- def clear_active_connections!
862
- connection_pool_list.each(&:release_connection)
1085
+ def clear_active_connections!(role = ActiveRecord::Base.current_role)
1086
+ connection_pool_list(role).each(&:release_connection)
863
1087
  end
864
1088
 
865
1089
  # Clears the cache which maps classes.
866
1090
  #
867
1091
  # See ConnectionPool#clear_reloadable_connections! for details.
868
- def clear_reloadable_connections!
869
- connection_pool_list.each(&:clear_reloadable_connections!)
1092
+ def clear_reloadable_connections!(role = ActiveRecord::Base.current_role)
1093
+ connection_pool_list(role).each(&:clear_reloadable_connections!)
1094
+ end
1095
+
1096
+ def clear_all_connections!(role = ActiveRecord::Base.current_role)
1097
+ connection_pool_list(role).each(&:disconnect!)
870
1098
  end
871
1099
 
872
- def clear_all_connections!
873
- connection_pool_list.each(&:disconnect!)
1100
+ # Disconnects all currently idle connections.
1101
+ #
1102
+ # See ConnectionPool#flush! for details.
1103
+ def flush_idle_connections!(role = ActiveRecord::Base.current_role)
1104
+ connection_pool_list(role).each(&:flush!)
874
1105
  end
875
1106
 
876
1107
  # Locate the connection of the nearest super class. This can be an
877
1108
  # active or defined connection: if it is the latter, it will be
878
1109
  # opened and set as the active connection for the class it was defined
879
1110
  # for (not necessarily the current class).
880
- def retrieve_connection(spec_name) #:nodoc:
881
- pool = retrieve_connection_pool(spec_name)
882
- raise ConnectionNotEstablished, "No connection pool with id #{spec_name} found." unless pool
883
- conn = pool.connection
884
- raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn
885
- conn
1111
+ def retrieve_connection(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
1112
+ pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
1113
+
1114
+ unless pool
1115
+ if shard != ActiveRecord::Base.default_shard
1116
+ message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard."
1117
+ elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
1118
+ message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
1119
+ elsif role != ActiveRecord::Base.default_role
1120
+ message = "No connection pool for '#{spec_name}' found for the '#{role}' role."
1121
+ else
1122
+ message = "No connection pool for '#{spec_name}' found."
1123
+ end
1124
+
1125
+ raise ConnectionNotEstablished, message
1126
+ end
1127
+
1128
+ pool.connection
886
1129
  end
887
1130
 
888
1131
  # Returns true if a connection that's accessible to this class has
889
1132
  # already been opened.
890
- def connected?(spec_name)
891
- conn = retrieve_connection_pool(spec_name)
892
- conn && conn.connected?
1133
+ def connected?(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
1134
+ pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
1135
+ pool && pool.connected?
893
1136
  end
894
1137
 
895
1138
  # Remove the connection for this class. This will close the active
896
1139
  # connection and the defined connection (if they exist). The result
897
- # can be used as an argument for establish_connection, for easily
1140
+ # can be used as an argument for #establish_connection, for easily
898
1141
  # re-establishing the connection.
899
- def remove_connection(spec_name)
900
- if pool = owner_to_pool.delete(spec_name)
901
- pool.automatic_reconnect = false
902
- pool.disconnect!
903
- pool.spec.config
1142
+ def remove_connection(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
1143
+ remove_connection_pool(owner, role: role, shard: shard)&.configuration_hash
1144
+ end
1145
+ deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash"
1146
+
1147
+ def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
1148
+ if pool_manager = get_pool_manager(owner)
1149
+ pool_config = pool_manager.remove_pool_config(role, shard)
1150
+
1151
+ if pool_config
1152
+ pool_config.disconnect!
1153
+ pool_config.db_config
1154
+ end
904
1155
  end
905
1156
  end
906
1157
 
907
- # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
1158
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
908
1159
  # This makes retrieving the connection pool O(1) once the process is warm.
909
1160
  # When a connection is established or removed, we invalidate the cache.
910
- #
911
- # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
912
- # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
913
- # #fetch is significantly slower than #[]. So in the nil case, no caching will
914
- # take place, but that's ok since the nil case is not the common one that we wish
915
- # to optimise for.
916
- def retrieve_connection_pool(spec_name)
917
- owner_to_pool.fetch(spec_name) do
918
- if ancestor_pool = pool_from_any_process_for(spec_name)
919
- # A connection was established in an ancestor process that must have
920
- # subsequently forked. We can't reuse the connection, but we can copy
921
- # the specification and establish a new connection with it.
922
- establish_connection(ancestor_pool.spec).tap do |pool|
923
- pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
924
- end
925
- else
926
- owner_to_pool[spec_name] = nil
927
- end
928
- end
1161
+ def retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
1162
+ pool_config = get_pool_manager(owner)&.get_pool_config(role, shard)
1163
+ pool_config&.pool
929
1164
  end
930
1165
 
931
1166
  private
1167
+ attr_reader :owner_to_pool_manager
932
1168
 
933
- def owner_to_pool
934
- @owner_to_pool[Process.pid]
935
- end
1169
+ # Returns the pool manager for an owner.
1170
+ #
1171
+ # Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is
1172
+ # deprecated in favor of looking it up by `"ActiveRecord::Base"`.
1173
+ #
1174
+ # During the deprecation period, if `"primary"` is passed, the pool manager
1175
+ # for `ActiveRecord::Base` will still be returned.
1176
+ def get_pool_manager(owner)
1177
+ return owner_to_pool_manager[owner] if owner_to_pool_manager.key?(owner)
1178
+
1179
+ if owner == "primary"
1180
+ ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 6.2.0. Please use `ActiveRecord::Base`.")
1181
+ owner_to_pool_manager[Base.name]
1182
+ end
1183
+ end
936
1184
 
937
- def pool_from_any_process_for(spec_name)
938
- owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
939
- owner_to_pool && owner_to_pool[spec_name]
940
- end
1185
+ # Returns an instance of PoolConfig for a given adapter.
1186
+ # Accepts a hash one layer deep that contains all connection information.
1187
+ #
1188
+ # == Example
1189
+ #
1190
+ # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
1191
+ # pool_config = Base.configurations.resolve_pool_config(:production)
1192
+ # pool_config.db_config.configuration_hash
1193
+ # # => { host: "localhost", database: "foo", adapter: "sqlite3" }
1194
+ #
1195
+ def resolve_pool_config(config, owner_name)
1196
+ db_config = Base.configurations.resolve(config)
1197
+
1198
+ raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
1199
+
1200
+ # Require the adapter itself and give useful feedback about
1201
+ # 1. Missing adapter gems and
1202
+ # 2. Adapter gems' missing dependencies.
1203
+ path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
1204
+ begin
1205
+ require path_to_adapter
1206
+ rescue LoadError => e
1207
+ # We couldn't require the adapter itself. Raise an exception that
1208
+ # points out config typos and missing gems.
1209
+ if e.path == path_to_adapter
1210
+ # We can assume that a non-builtin adapter was specified, so it's
1211
+ # either misspelled or missing from Gemfile.
1212
+ raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
1213
+
1214
+ # Bubbled up from the adapter require. Prefix the exception message
1215
+ # with some guidance about how to address it and reraise.
1216
+ else
1217
+ raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
1218
+ end
1219
+ end
1220
+
1221
+ unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
1222
+ raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
1223
+ end
1224
+
1225
+ ConnectionAdapters::PoolConfig.new(owner_name, db_config)
1226
+ end
941
1227
  end
942
1228
  end
943
1229
  end