activerecord 6.1.7 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -1385
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  30. data/lib/active_record/associations/join_dependency.rb +23 -15
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +404 -509
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +2 -14
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +51 -49
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +18 -34
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +276 -251
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +56 -0
  165. data/lib/active_record/enum.rb +163 -63
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +56 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +143 -159
  199. data/lib/active_record/nested_attributes.rb +48 -21
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +19 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +234 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +325 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +198 -63
  216. data/lib/active_record/relation/calculations.rb +300 -111
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +842 -150
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +5 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +277 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +64 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/to_sql.rb +170 -36
  317. data/lib/arel/visitors/visitor.rb +2 -2
  318. data/lib/arel.rb +23 -4
  319. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  320. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  321. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  322. data/lib/rails/generators/active_record/migration.rb +3 -1
  323. data/lib/rails/generators/active_record/model/USAGE +113 -0
  324. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  325. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  326. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  328. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  329. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  330. metadata +100 -14
  331. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  332. data/lib/active_record/null_relation.rb +0 -67
@@ -3,32 +3,56 @@
3
3
  require "thread"
4
4
  require "concurrent/map"
5
5
  require "monitor"
6
- require "weakref"
6
+
7
+ require "active_record/connection_adapters/abstract/connection_pool/queue"
8
+ require "active_record/connection_adapters/abstract/connection_pool/reaper"
7
9
 
8
10
  module ActiveRecord
9
11
  module ConnectionAdapters
10
12
  module AbstractPool # :nodoc:
11
- def get_schema_cache(connection)
12
- self.schema_cache ||= SchemaCache.new(connection)
13
- schema_cache.connection = connection
14
- schema_cache
15
- end
16
-
17
- def set_schema_cache(cache)
18
- self.schema_cache = cache
19
- end
20
13
  end
21
14
 
22
15
  class NullPool # :nodoc:
23
16
  include ConnectionAdapters::AbstractPool
24
17
 
25
- attr_accessor :schema_cache
18
+ class NullConfig # :nodoc:
19
+ def method_missing(...)
20
+ nil
21
+ end
22
+ end
23
+ NULL_CONFIG = NullConfig.new # :nodoc:
24
+
25
+ def initialize
26
+ super()
27
+ @mutex = Mutex.new
28
+ @server_version = nil
29
+ end
30
+
31
+ def server_version(connection) # :nodoc:
32
+ @server_version || @mutex.synchronize { @server_version ||= connection.get_database_version }
33
+ end
34
+
35
+ def schema_reflection
36
+ SchemaReflection.new(nil)
37
+ end
38
+
39
+ def schema_cache; end
40
+ def connection_class; end
41
+ def checkin(_); end
42
+ def remove(_); end
43
+ def async_executor; end
44
+
45
+ def db_config
46
+ NULL_CONFIG
47
+ end
26
48
 
27
- def connection_klass
28
- nil
49
+ def dirties_query_cache
50
+ true
29
51
  end
30
52
  end
31
53
 
54
+ # = Active Record Connection Pool
55
+ #
32
56
  # Connection pool base class for managing Active Record database
33
57
  # connections.
34
58
  #
@@ -43,19 +67,17 @@ module ActiveRecord
43
67
  # handle cases in which there are more threads than connections: if all
44
68
  # connections have been checked out, and a thread tries to checkout a
45
69
  # connection anyway, then ConnectionPool will wait until some other thread
46
- # has checked in a connection.
70
+ # has checked in a connection, or the +checkout_timeout+ has expired.
47
71
  #
48
72
  # == Obtaining (checking out) a connection
49
73
  #
50
74
  # Connections can be obtained and used from a connection pool in several
51
75
  # ways:
52
76
  #
53
- # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
54
- # as with Active Record 2.1 and
55
- # earlier (pre-connection-pooling). Eventually, when you're done with
56
- # the connection(s) and wish it to be returned to the pool, you call
57
- # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
58
- # This will be the default behavior for Active Record when used in conjunction with
77
+ # 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection].
78
+ # When you're done with the connection(s) and wish it to be returned to the pool, you call
79
+ # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
80
+ # This is the default behavior for Active Record when used in conjunction with
59
81
  # Action Pack's request handling cycle.
60
82
  # 2. Manually check out a connection from the pool with
61
83
  # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
@@ -68,6 +90,12 @@ module ActiveRecord
68
90
  # Connections in the pool are actually AbstractAdapter objects (or objects
69
91
  # compatible with AbstractAdapter's interface).
70
92
  #
93
+ # While a thread has a connection checked out from the pool using one of the
94
+ # above three methods, that connection will automatically be the one used
95
+ # by ActiveRecord queries executing on that thread. It is not required to
96
+ # explicitly pass the checked out connection to \Rails models or queries, for
97
+ # example.
98
+ #
71
99
  # == Options
72
100
  #
73
101
  # There are several connection-pooling-related options that you can add to
@@ -90,279 +118,98 @@ module ActiveRecord
90
118
  # * private methods that require being called in a +synchronize+ blocks
91
119
  # are now explicitly documented
92
120
  class ConnectionPool
93
- # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
94
- # with which it shares a Monitor.
95
- class Queue
96
- def initialize(lock = Monitor.new)
97
- @lock = lock
98
- @cond = @lock.new_cond
99
- @num_waiting = 0
100
- @queue = []
101
- end
121
+ class Lease # :nodoc:
122
+ attr_accessor :connection, :sticky
102
123
 
103
- # Test if any threads are currently waiting on the queue.
104
- def any_waiting?
105
- synchronize do
106
- @num_waiting > 0
107
- end
124
+ def initialize
125
+ @connection = nil
126
+ @sticky = nil
108
127
  end
109
128
 
110
- # Returns the number of threads currently waiting on this
111
- # queue.
112
- def num_waiting
113
- synchronize do
114
- @num_waiting
115
- end
129
+ def release
130
+ conn = @connection
131
+ @connection = nil
132
+ @sticky = nil
133
+ conn
116
134
  end
117
135
 
118
- # Add +element+ to the queue. Never blocks.
119
- def add(element)
120
- synchronize do
121
- @queue.push element
122
- @cond.signal
123
- end
124
- end
125
-
126
- # If +element+ is in the queue, remove and return it, or +nil+.
127
- def delete(element)
128
- synchronize do
129
- @queue.delete(element)
130
- end
131
- end
132
-
133
- # Remove all elements from the queue.
134
- def clear
135
- synchronize do
136
- @queue.clear
136
+ def clear(connection)
137
+ if @connection == connection
138
+ @connection = nil
139
+ @sticky = nil
140
+ true
141
+ else
142
+ false
137
143
  end
138
144
  end
145
+ end
139
146
 
140
- # Remove the head of the queue.
141
- #
142
- # If +timeout+ is not given, remove and return the head of the
143
- # queue if the number of available elements is strictly
144
- # greater than the number of threads currently waiting (that
145
- # is, don't jump ahead in line). Otherwise, return +nil+.
146
- #
147
- # If +timeout+ is given, block if there is no element
148
- # available, waiting up to +timeout+ seconds for an element to
149
- # become available.
150
- #
151
- # Raises:
152
- # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
153
- # becomes available within +timeout+ seconds,
154
- def poll(timeout = nil)
155
- synchronize { internal_poll(timeout) }
156
- end
157
-
158
- private
159
- def internal_poll(timeout)
160
- no_wait_poll || (timeout && wait_poll(timeout))
161
- end
162
-
163
- def synchronize(&block)
164
- @lock.synchronize(&block)
165
- end
166
-
167
- # Test if the queue currently contains any elements.
168
- def any?
169
- !@queue.empty?
170
- end
171
-
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
179
-
180
- # Removes and returns the head of the queue if possible, or +nil+.
181
- def remove
182
- @queue.pop
183
- end
184
-
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
147
+ class LeaseRegistry # :nodoc:
148
+ if ObjectSpace.const_defined?(:WeakKeyMap) # RUBY_VERSION >= 3.3
149
+ WeakKeyMap = ::ObjectSpace::WeakKeyMap # :nodoc:
150
+ else
151
+ class WeakKeyMap # :nodoc:
152
+ def initialize
153
+ @map = ObjectSpace::WeakMap.new
154
+ @values = nil
155
+ @size = 0
156
+ end
191
157
 
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
158
+ alias_method :clear, :initialize
196
159
 
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
160
+ def [](key)
161
+ prune if @map.size != @size
162
+ @map[key]
163
+ end
203
164
 
204
- return remove if any?
165
+ def []=(key, value)
166
+ @map[key] = value
167
+ prune if @map.size != @size
168
+ value
169
+ end
205
170
 
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
171
+ def delete(key)
172
+ if value = self[key]
173
+ self[key] = nil
174
+ prune
211
175
  end
176
+ value
212
177
  end
213
- ensure
214
- @num_waiting -= 1
215
- end
216
- end
217
-
218
- # Adds the ability to turn a basic fair FIFO queue into one
219
- # biased to some thread.
220
- module BiasableQueue # :nodoc:
221
- class BiasedConditionVariable # :nodoc:
222
- # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
223
- # +signal+ and +wait+ methods are only called while holding a lock
224
- def initialize(lock, other_cond, preferred_thread)
225
- @real_cond = lock.new_cond
226
- @other_cond = other_cond
227
- @preferred_thread = preferred_thread
228
- @num_waiting_on_real_cond = 0
229
- end
230
-
231
- def broadcast
232
- broadcast_on_biased
233
- @other_cond.broadcast
234
- end
235
178
 
236
- def broadcast_on_biased
237
- @num_waiting_on_real_cond = 0
238
- @real_cond.broadcast
239
- end
240
-
241
- def signal
242
- if @num_waiting_on_real_cond > 0
243
- @num_waiting_on_real_cond -= 1
244
- @real_cond
245
- else
246
- @other_cond
247
- end.signal
248
- end
249
-
250
- def wait(timeout)
251
- if Thread.current == @preferred_thread
252
- @num_waiting_on_real_cond += 1
253
- @real_cond
254
- else
255
- @other_cond
256
- end.wait(timeout)
179
+ private
180
+ def prune(force = false)
181
+ @values = @map.values
182
+ @size = @map.size
183
+ end
257
184
  end
258
185
  end
259
186
 
260
- def with_a_bias_for(thread)
261
- previous_cond = nil
262
- new_cond = nil
263
- synchronize do
264
- previous_cond = @cond
265
- @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
266
- end
267
- yield
268
- ensure
269
- synchronize do
270
- @cond = previous_cond if previous_cond
271
- new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
272
- end
187
+ def initialize
188
+ @mutex = Mutex.new
189
+ @map = WeakKeyMap.new
273
190
  end
274
- end
275
191
 
276
- # Connections must be leased while holding the main pool mutex. This is
277
- # an internal subclass that also +.leases+ returned connections while
278
- # still in queue's critical section (queue synchronizes with the same
279
- # <tt>@lock</tt> as the main pool) so that a returned connection is already
280
- # leased and there is no need to re-enter synchronized block.
281
- class ConnectionLeasingQueue < Queue # :nodoc:
282
- include BiasableQueue
283
-
284
- private
285
- def internal_poll(timeout)
286
- conn = super
287
- conn.lease if conn
288
- conn
192
+ def [](context)
193
+ @mutex.synchronize do
194
+ @map[context] ||= Lease.new
289
195
  end
290
- end
291
-
292
- # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
293
- # +pool+. A reaper instantiated with a zero frequency will never reap
294
- # the connection pool.
295
- #
296
- # Configure the frequency by setting +reaping_frequency+ in your database
297
- # yaml file (default 60 seconds).
298
- class Reaper
299
- attr_reader :pool, :frequency
300
-
301
- def initialize(pool, frequency)
302
- @pool = pool
303
- @frequency = frequency
304
196
  end
305
197
 
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
198
+ def clear
199
+ @mutex.synchronize do
200
+ @map = WeakKeyMap.new
319
201
  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
349
- end
350
- end
351
-
352
- def run
353
- return unless frequency && frequency > 0
354
- self.class.register_pool(pool, frequency)
355
202
  end
356
203
  end
357
204
 
358
205
  include MonitorMixin
359
- include QueryCache::ConnectionPoolConfiguration
206
+ prepend QueryCache::ConnectionPoolConfiguration
360
207
  include ConnectionAdapters::AbstractPool
361
208
 
362
209
  attr_accessor :automatic_reconnect, :checkout_timeout
363
- attr_reader :db_config, :size, :reaper, :pool_config, :connection_klass
210
+ attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
364
211
 
365
- delegate :schema_cache, :schema_cache=, to: :pool_config
212
+ delegate :schema_reflection, :server_version, to: :pool_config
366
213
 
367
214
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
368
215
  # object which describes database connection information (e.g. adapter,
@@ -375,7 +222,8 @@ module ActiveRecord
375
222
 
376
223
  @pool_config = pool_config
377
224
  @db_config = pool_config.db_config
378
- @connection_klass = pool_config.connection_klass
225
+ @role = pool_config.role
226
+ @shard = pool_config.shard
379
227
 
380
228
  @checkout_timeout = db_config.checkout_timeout
381
229
  @idle_timeout = db_config.idle_timeout
@@ -389,9 +237,9 @@ module ActiveRecord
389
237
  # then that +thread+ does indeed own that +conn+. However, an absence of such
390
238
  # mapping does not mean that the +thread+ doesn't own the said connection. In
391
239
  # that case +conn.owner+ attr should be consulted.
392
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
240
+ # Access and modification of <tt>@leases</tt> does not require
393
241
  # synchronization.
394
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
242
+ @leases = LeaseRegistry.new
395
243
 
396
244
  @connections = []
397
245
  @automatic_reconnect = true
@@ -404,69 +252,175 @@ module ActiveRecord
404
252
  @threads_blocking_new_connections = 0
405
253
 
406
254
  @available = ConnectionLeasingQueue.new self
255
+ @pinned_connection = nil
256
+ @pinned_connections_depth = 0
257
+
258
+ @async_executor = build_async_executor
407
259
 
408
- @lock_thread = false
260
+ @schema_cache = nil
409
261
 
410
262
  @reaper = Reaper.new(self, db_config.reaping_frequency)
411
263
  @reaper.run
412
264
  end
413
265
 
414
- def lock_thread=(lock_thread)
415
- if lock_thread
416
- @lock_thread = Thread.current
417
- else
418
- @lock_thread = nil
419
- end
266
+ def inspect # :nodoc:
267
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
268
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
269
+
270
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
271
+ end
272
+
273
+ def schema_cache
274
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
275
+ end
276
+
277
+ def schema_reflection=(schema_reflection)
278
+ pool_config.schema_reflection = schema_reflection
279
+ @schema_cache = nil
280
+ end
281
+
282
+ def migration_context # :nodoc:
283
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
284
+ end
285
+
286
+ def migrations_paths # :nodoc:
287
+ db_config.migrations_paths || Migrator.migrations_paths
288
+ end
289
+
290
+ def schema_migration # :nodoc:
291
+ SchemaMigration.new(self)
292
+ end
293
+
294
+ def internal_metadata # :nodoc:
295
+ InternalMetadata.new(self)
420
296
  end
421
297
 
422
298
  # Retrieve the connection associated with the current thread, or call
423
299
  # #checkout to obtain one if necessary.
424
300
  #
425
- # #connection can be called any number of times; the connection is
301
+ # #lease_connection can be called any number of times; the connection is
426
302
  # held in a cache keyed by a thread.
303
+ def lease_connection
304
+ lease = connection_lease
305
+ lease.sticky = true
306
+ lease.connection ||= checkout
307
+ end
308
+
309
+ def permanent_lease? # :nodoc:
310
+ connection_lease.sticky.nil?
311
+ end
312
+
427
313
  def connection
428
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
314
+ ActiveRecord.deprecator.warn(<<~MSG)
315
+ ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
316
+ and will be removed in Rails 8.0. Use #lease_connection instead.
317
+ MSG
318
+ lease_connection
319
+ end
320
+
321
+ def pin_connection!(lock_thread) # :nodoc:
322
+ @pinned_connection ||= (connection_lease&.connection || checkout)
323
+ @pinned_connections_depth += 1
324
+
325
+ # Any leased connection must be in @connections otherwise
326
+ # some methods like #connected? won't behave correctly
327
+ unless @connections.include?(@pinned_connection)
328
+ @connections << @pinned_connection
329
+ end
330
+
331
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
332
+ @pinned_connection.verify! # eagerly validate the connection
333
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
334
+ end
335
+
336
+ def unpin_connection! # :nodoc:
337
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
338
+
339
+ clean = true
340
+ @pinned_connection.lock.synchronize do
341
+ @pinned_connections_depth -= 1
342
+ connection = @pinned_connection
343
+ @pinned_connection = nil if @pinned_connections_depth.zero?
344
+
345
+ if connection.transaction_open?
346
+ connection.rollback_transaction
347
+ else
348
+ # Something committed or rolled back the transaction
349
+ clean = false
350
+ connection.reset!
351
+ end
352
+
353
+ if @pinned_connection.nil?
354
+ connection.lock_thread = nil
355
+ checkin(connection)
356
+ end
357
+ end
358
+
359
+ clean
360
+ end
361
+
362
+ def connection_class # :nodoc:
363
+ pool_config.connection_class
429
364
  end
430
365
 
431
366
  # Returns true if there is an open connection being used for the current thread.
432
367
  #
433
368
  # This method only works for connections that have been obtained through
434
- # #connection or #with_connection methods. Connections obtained through
369
+ # #lease_connection or #with_connection methods. Connections obtained through
435
370
  # #checkout will not be detected by #active_connection?
436
371
  def active_connection?
437
- @thread_cached_conns[connection_cache_key(current_thread)]
372
+ connection_lease.connection
438
373
  end
374
+ alias_method :active_connection, :active_connection? # :nodoc:
439
375
 
440
376
  # Signal that the thread is finished with the current connection.
441
377
  # #release_connection releases the connection-thread association
442
378
  # and returns the connection to the pool.
443
379
  #
444
380
  # This method only works for connections that have been obtained through
445
- # #connection or #with_connection methods, connections obtained through
381
+ # #lease_connection or #with_connection methods, connections obtained through
446
382
  # #checkout will not be automatically released.
447
- def release_connection(owner_thread = Thread.current)
448
- if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
383
+ def release_connection(existing_lease = nil)
384
+ if conn = connection_lease.release
449
385
  checkin conn
386
+ return true
387
+ end
388
+ false
389
+ end
390
+
391
+ # Yields a connection from the connection pool to the block. If no connection
392
+ # is already checked out by the current thread, a connection will be checked
393
+ # out from the pool, yielded to the block, and then returned to the pool when
394
+ # the block is finished. If a connection has already been checked out on the
395
+ # current thread, such as via #lease_connection or #with_connection, that existing
396
+ # connection will be the one yielded and it will not be returned to the pool
397
+ # automatically at the end of the block; it is expected that such an existing
398
+ # connection will be properly returned to the pool by the code that checked
399
+ # it out.
400
+ def with_connection(prevent_permanent_checkout: false)
401
+ lease = connection_lease
402
+ sticky_was = lease.sticky
403
+ lease.sticky = false if prevent_permanent_checkout
404
+
405
+ if lease.connection
406
+ begin
407
+ yield lease.connection
408
+ ensure
409
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
410
+ end
411
+ else
412
+ begin
413
+ yield lease.connection = checkout
414
+ ensure
415
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
416
+ release_connection(lease) unless lease.sticky
417
+ end
450
418
  end
451
419
  end
452
420
 
453
- # If a connection obtained through #connection or #with_connection methods
454
- # already exists yield it to the block. If no such connection
455
- # exists checkout a connection, yield it to the block, and checkin the
456
- # connection when finished.
457
- def with_connection
458
- unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
459
- conn = connection
460
- fresh_connection = true
461
- end
462
- yield conn
463
- ensure
464
- release_connection if fresh_connection
465
- end
466
-
467
421
  # Returns true if a connection has already been opened.
468
422
  def connected?
469
- synchronize { @connections.any? }
423
+ synchronize { @connections.any?(&:connected?) }
470
424
  end
471
425
 
472
426
  # Returns an array containing the connections currently in the pool.
@@ -501,6 +455,7 @@ module ActiveRecord
501
455
  conn.disconnect!
502
456
  end
503
457
  @connections = []
458
+ @leases.clear
504
459
  @available.clear
505
460
  end
506
461
  end
@@ -527,7 +482,7 @@ module ActiveRecord
527
482
  @connections.each do |conn|
528
483
  conn.discard!
529
484
  end
530
- @connections = @available = @thread_cached_conns = nil
485
+ @connections = @available = @leases = nil
531
486
  end
532
487
  end
533
488
 
@@ -585,7 +540,21 @@ module ActiveRecord
585
540
  # Raises:
586
541
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
587
542
  def checkout(checkout_timeout = @checkout_timeout)
588
- checkout_and_verify(acquire_connection(checkout_timeout))
543
+ if @pinned_connection
544
+ @pinned_connection.lock.synchronize do
545
+ synchronize do
546
+ @pinned_connection.verify!
547
+ # Any leased connection must be in @connections otherwise
548
+ # some methods like #connected? won't behave correctly
549
+ unless @connections.include?(@pinned_connection)
550
+ @connections << @pinned_connection
551
+ end
552
+ end
553
+ end
554
+ @pinned_connection
555
+ else
556
+ checkout_and_verify(acquire_connection(checkout_timeout))
557
+ end
589
558
  end
590
559
 
591
560
  # Check-in a database connection back into the pool, indicating that you
@@ -594,9 +563,11 @@ module ActiveRecord
594
563
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
595
564
  # calling #checkout on this pool.
596
565
  def checkin(conn)
566
+ return if @pinned_connection.equal?(conn)
567
+
597
568
  conn.lock.synchronize do
598
569
  synchronize do
599
- remove_connection_from_thread_cache conn
570
+ connection_lease.clear(conn)
600
571
 
601
572
  conn._run_checkin_callbacks do
602
573
  conn.expire
@@ -659,6 +630,8 @@ module ActiveRecord
659
630
  remove conn
660
631
  end
661
632
  end
633
+
634
+ prune_thread_cache
662
635
  end
663
636
 
664
637
  # Disconnect all connections that have been idle for at least
@@ -695,8 +668,7 @@ module ActiveRecord
695
668
  @available.num_waiting
696
669
  end
697
670
 
698
- # Return connection pool's usage statistic
699
- # Example:
671
+ # Returns the connection pool's usage statistic.
700
672
  #
701
673
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
702
674
  def stat
@@ -713,7 +685,32 @@ module ActiveRecord
713
685
  end
714
686
  end
715
687
 
688
+ def schedule_query(future_result) # :nodoc:
689
+ @async_executor.post { future_result.execute_or_skip }
690
+ Thread.pass
691
+ end
692
+
716
693
  private
694
+ def connection_lease
695
+ @leases[ActiveSupport::IsolatedExecutionState.context]
696
+ end
697
+
698
+ def build_async_executor
699
+ case ActiveRecord.async_query_executor
700
+ when :multi_thread_pool
701
+ if @db_config.max_threads > 0
702
+ Concurrent::ThreadPoolExecutor.new(
703
+ min_threads: @db_config.min_threads,
704
+ max_threads: @db_config.max_threads,
705
+ max_queue: @db_config.max_queue,
706
+ fallback_policy: :caller_runs
707
+ )
708
+ end
709
+ when :global_thread_pool
710
+ ActiveRecord.global_thread_pool_async_query_executor
711
+ end
712
+ end
713
+
717
714
  #--
718
715
  # this is unfortunately not concurrent
719
716
  def bulk_make_new_connections(num_new_conns_needed)
@@ -726,19 +723,6 @@ module ActiveRecord
726
723
  end
727
724
  end
728
725
 
729
- #--
730
- # From the discussion on GitHub:
731
- # https://github.com/rails/rails/pull/14938#commitcomment-6601951
732
- # This hook-in method allows for easier monkey-patching fixes needed by
733
- # JRuby users that use Fibers.
734
- def connection_cache_key(thread)
735
- thread
736
- end
737
-
738
- def current_thread
739
- @lock_thread || Thread.current
740
- end
741
-
742
726
  # Take control of all existing connections so a "group" action such as
743
727
  # reload/disconnect can be performed safely. It is no longer enough to
744
728
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -752,18 +736,21 @@ module ActiveRecord
752
736
 
753
737
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
754
738
  collected_conns = synchronize do
739
+ reap # No need to wait for dead owners
740
+
755
741
  # account for our own connections
756
- @connections.select { |conn| conn.owner == Thread.current }
742
+ @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
757
743
  end
758
744
 
759
745
  newly_checked_out = []
760
- timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
746
+ timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
761
747
 
762
- @available.with_a_bias_for(Thread.current) do
748
+ @available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
763
749
  loop do
764
750
  synchronize do
765
751
  return if collected_conns.size == @connections.size && @now_connecting == 0
766
- remaining_timeout = timeout_time - Concurrent.monotonic_time
752
+
753
+ remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
767
754
  remaining_timeout = 0 if remaining_timeout < 0
768
755
  conn = checkout_for_exclusive_access(remaining_timeout)
769
756
  collected_conns << conn
@@ -806,14 +793,14 @@ module ActiveRecord
806
793
 
807
794
  thread_report = []
808
795
  @connections.each do |conn|
809
- unless conn.owner == Thread.current
796
+ unless conn.owner == ActiveSupport::IsolatedExecutionState.context
810
797
  thread_report << "#{conn} is owned by #{conn.owner}"
811
798
  end
812
799
  end
813
800
 
814
801
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
815
802
 
816
- raise ExclusiveConnectionTimeoutError, msg
803
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
817
804
  end
818
805
 
819
806
  def with_new_connections_blocked
@@ -867,21 +854,31 @@ module ActiveRecord
867
854
  conn
868
855
  else
869
856
  reap
870
- @available.poll(checkout_timeout)
857
+ # Retry after reaping, which may return an available connection,
858
+ # remove an inactive connection, or both
859
+ if conn = @available.poll || try_to_checkout_new_connection
860
+ conn
861
+ else
862
+ @available.poll(checkout_timeout)
863
+ end
871
864
  end
865
+ rescue ConnectionTimeoutError => ex
866
+ raise ex.set_pool(self)
872
867
  end
873
868
 
874
869
  #--
875
870
  # if owner_thread param is omitted, this must be called in synchronize block
876
871
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
877
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
872
+ @leases[owner_thread].clear(conn)
878
873
  end
879
874
  alias_method :release, :remove_connection_from_thread_cache
880
875
 
881
876
  def new_connection
882
- Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
883
- conn.check_version
884
- end
877
+ connection = db_config.new_connection
878
+ connection.pool = self
879
+ connection
880
+ rescue ConnectionNotEstablished => ex
881
+ raise ex.set_pool(self)
885
882
  end
886
883
 
887
884
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
@@ -919,6 +916,12 @@ module ActiveRecord
919
916
  def adopt_connection(conn)
920
917
  conn.pool = self
921
918
  @connections << conn
919
+
920
+ # We just created the first connection, it's time to load the schema
921
+ # cache if that wasn't eagerly done before
922
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
923
+ schema_cache.load!
924
+ end
922
925
  end
923
926
 
924
927
  def checkout_new_connection
@@ -928,302 +931,14 @@ module ActiveRecord
928
931
 
929
932
  def checkout_and_verify(c)
930
933
  c._run_checkout_callbacks do
931
- c.verify!
934
+ c.clean!
932
935
  end
933
936
  c
934
- rescue
937
+ rescue Exception
935
938
  remove c
936
939
  c.disconnect!
937
940
  raise
938
941
  end
939
942
  end
940
-
941
- # ConnectionHandler is a collection of ConnectionPool objects. It is used
942
- # for keeping separate connection pools that connect to different databases.
943
- #
944
- # For example, suppose that you have 5 models, with the following hierarchy:
945
- #
946
- # class Author < ActiveRecord::Base
947
- # end
948
- #
949
- # class BankAccount < ActiveRecord::Base
950
- # end
951
- #
952
- # class Book < ActiveRecord::Base
953
- # establish_connection :library_db
954
- # end
955
- #
956
- # class ScaryBook < Book
957
- # end
958
- #
959
- # class GoodBook < Book
960
- # end
961
- #
962
- # And a database.yml that looked like this:
963
- #
964
- # development:
965
- # database: my_application
966
- # host: localhost
967
- #
968
- # library_db:
969
- # database: library
970
- # host: some.library.org
971
- #
972
- # Your primary database in the development environment is "my_application"
973
- # but the Book model connects to a separate database called "library_db"
974
- # (this can even be a database on a different machine).
975
- #
976
- # Book, ScaryBook and GoodBook will all use the same connection pool to
977
- # "library_db" while Author, BankAccount, and any other models you create
978
- # will use the default connection pool to "my_application".
979
- #
980
- # The various connection pools are managed by a single instance of
981
- # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
982
- # All Active Record models use this handler to determine the connection pool that they
983
- # should use.
984
- #
985
- # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
986
- # about the model. The model needs to pass a connection specification name to the handler,
987
- # in order to look up the correct connection pool.
988
- class ConnectionHandler
989
- FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
990
- private_constant :FINALIZER
991
-
992
- def initialize
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"
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) }
1036
- end
1037
-
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) }
1040
- end
1041
- alias :connection_pools :connection_pool_list
1042
-
1043
- def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
1044
- owner_name = config.to_s if config.is_a?(Symbol)
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
1074
- end
1075
-
1076
- # Returns true if there are any active connections among the connection
1077
- # pools that the ConnectionHandler is managing.
1078
- def active_connections?(role = ActiveRecord::Base.current_role)
1079
- connection_pool_list(role).any?(&:active_connection?)
1080
- end
1081
-
1082
- # Returns any connections in use by the current thread back to the pool,
1083
- # and also returns connections to the pool cached by threads that are no
1084
- # longer alive.
1085
- def clear_active_connections!(role = ActiveRecord::Base.current_role)
1086
- connection_pool_list(role).each(&:release_connection)
1087
- end
1088
-
1089
- # Clears the cache which maps classes.
1090
- #
1091
- # See ConnectionPool#clear_reloadable_connections! for details.
1092
- def clear_reloadable_connections!(role = ActiveRecord::Base.current_role)
1093
- connection_pool_list(role).each(&:clear_reloadable_connections!)
1094
- end
1095
-
1096
- def clear_all_connections!(role = ActiveRecord::Base.current_role)
1097
- connection_pool_list(role).each(&:disconnect!)
1098
- end
1099
-
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!)
1105
- end
1106
-
1107
- # Locate the connection of the nearest super class. This can be an
1108
- # active or defined connection: if it is the latter, it will be
1109
- # opened and set as the active connection for the class it was defined
1110
- # for (not necessarily the current class).
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
1129
- end
1130
-
1131
- # Returns true if a connection that's accessible to this class has
1132
- # already been opened.
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?
1136
- end
1137
-
1138
- # Remove the connection for this class. This will close the active
1139
- # connection and the defined connection (if they exist). The result
1140
- # can be used as an argument for #establish_connection, for easily
1141
- # re-establishing the connection.
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
1155
- end
1156
- end
1157
-
1158
- # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
1159
- # This makes retrieving the connection pool O(1) once the process is warm.
1160
- # When a connection is established or removed, we invalidate the cache.
1161
- def retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
1162
- pool_config = get_pool_manager(owner)&.get_pool_config(role, shard)
1163
- pool_config&.pool
1164
- end
1165
-
1166
- private
1167
- attr_reader :owner_to_pool_manager
1168
-
1169
- # Returns the pool manager for an owner.
1170
- #
1171
- # Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is
1172
- # deprecated in favor of looking it up by `"ActiveRecord::Base"`.
1173
- #
1174
- # During the deprecation period, if `"primary"` is passed, the pool manager
1175
- # for `ActiveRecord::Base` will still be returned.
1176
- def get_pool_manager(owner)
1177
- return owner_to_pool_manager[owner] if owner_to_pool_manager.key?(owner)
1178
-
1179
- if owner == "primary"
1180
- ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 7.0.0. Please use `ActiveRecord::Base`.")
1181
- owner_to_pool_manager[Base.name]
1182
- end
1183
- end
1184
-
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
1227
- end
1228
943
  end
1229
944
  end