activerecord 6.1.7 → 7.2.2

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 (333) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +616 -1290
  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 +19 -8
  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 +30 -27
  30. data/lib/active_record/associations/join_dependency.rb +28 -20
  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 +429 -522
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +1 -5
  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 +15 -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 +57 -54
  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 +19 -35
  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 +325 -604
  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 +230 -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 +378 -143
  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 +348 -165
  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 +403 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
  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 +310 -253
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
  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 +58 -0
  165. data/lib/active_record/enum.rb +170 -62
  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 +59 -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 +145 -158
  199. data/lib/active_record/nested_attributes.rb +61 -23
  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 +18 -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 +229 -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 +332 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +200 -65
  216. data/lib/active_record/relation/calculations.rb +301 -112
  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 +870 -163
  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 +6 -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 +288 -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 +65 -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/sqlite.rb +25 -0
  317. data/lib/arel/visitors/to_sql.rb +170 -36
  318. data/lib/arel/visitors/visitor.rb +2 -2
  319. data/lib/arel.rb +23 -4
  320. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  321. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  322. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  323. data/lib/rails/generators/active_record/migration.rb +3 -1
  324. data/lib/rails/generators/active_record/model/USAGE +113 -0
  325. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  326. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  328. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  329. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  330. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  331. metadata +103 -17
  332. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  333. 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:
26
24
 
27
- def connection_klass
28
- nil
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
48
+
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,105 @@ 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 = []
121
+ class WeakThreadKeyMap # :nodoc:
122
+ # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
123
+ # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
124
+ def initialize
125
+ @map = {}
101
126
  end
102
127
 
103
- # Test if any threads are currently waiting on the queue.
104
- def any_waiting?
105
- synchronize do
106
- @num_waiting > 0
107
- end
128
+ def clear
129
+ @map.clear
108
130
  end
109
131
 
110
- # Returns the number of threads currently waiting on this
111
- # queue.
112
- def num_waiting
113
- synchronize do
114
- @num_waiting
115
- end
132
+ def [](key)
133
+ @map[key]
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
136
+ def []=(key, value)
137
+ @map.select! { |c, _| c.alive? }
138
+ @map[key] = value
124
139
  end
140
+ end
125
141
 
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
142
+ class Lease # :nodoc:
143
+ attr_accessor :connection, :sticky
132
144
 
133
- # Remove all elements from the queue.
134
- def clear
135
- synchronize do
136
- @queue.clear
137
- end
145
+ def initialize
146
+ @connection = nil
147
+ @sticky = nil
138
148
  end
139
149
 
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) }
150
+ def release
151
+ conn = @connection
152
+ @connection = nil
153
+ @sticky = nil
154
+ conn
156
155
  end
157
156
 
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
191
-
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
196
-
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
203
-
204
- return remove if any?
205
-
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
212
- end
213
- ensure
214
- @num_waiting -= 1
157
+ def clear(connection)
158
+ if @connection == connection
159
+ @connection = nil
160
+ @sticky = nil
161
+ true
162
+ else
163
+ false
215
164
  end
165
+ end
216
166
  end
217
167
 
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
-
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)
257
- end
168
+ class LeaseRegistry # :nodoc:
169
+ def initialize
170
+ @mutex = Mutex.new
171
+ @map = WeakThreadKeyMap.new
258
172
  end
259
173
 
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
174
+ def [](context)
175
+ @mutex.synchronize do
176
+ @map[context] ||= Lease.new
272
177
  end
273
178
  end
274
- end
275
179
 
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
180
+ def clear
181
+ @mutex.synchronize do
182
+ @map.clear
289
183
  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
184
  end
185
+ end
305
186
 
306
- @mutex = Mutex.new
307
- @pools = {}
308
- @threads = {}
309
-
187
+ module ExecutorHooks # :nodoc:
310
188
  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
189
+ def run
190
+ # noop
319
191
  end
320
192
 
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
193
+ def complete(_)
194
+ ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
195
+ if (connection = pool.active_connection?)
196
+ transaction = connection.current_transaction
197
+ if transaction.closed? || !transaction.joinable?
198
+ pool.release_connection
347
199
  end
348
200
  end
349
201
  end
202
+ end
350
203
  end
204
+ end
351
205
 
352
- def run
353
- return unless frequency && frequency > 0
354
- self.class.register_pool(pool, frequency)
206
+ class << self
207
+ def install_executor_hooks(executor = ActiveSupport::Executor)
208
+ executor.register_hook(ExecutorHooks)
355
209
  end
356
210
  end
357
211
 
358
212
  include MonitorMixin
359
- include QueryCache::ConnectionPoolConfiguration
213
+ prepend QueryCache::ConnectionPoolConfiguration
360
214
  include ConnectionAdapters::AbstractPool
361
215
 
362
216
  attr_accessor :automatic_reconnect, :checkout_timeout
363
- attr_reader :db_config, :size, :reaper, :pool_config, :connection_klass
217
+ attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
364
218
 
365
- delegate :schema_cache, :schema_cache=, to: :pool_config
219
+ delegate :schema_reflection, :server_version, to: :pool_config
366
220
 
367
221
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
368
222
  # object which describes database connection information (e.g. adapter,
@@ -375,7 +229,8 @@ module ActiveRecord
375
229
 
376
230
  @pool_config = pool_config
377
231
  @db_config = pool_config.db_config
378
- @connection_klass = pool_config.connection_klass
232
+ @role = pool_config.role
233
+ @shard = pool_config.shard
379
234
 
380
235
  @checkout_timeout = db_config.checkout_timeout
381
236
  @idle_timeout = db_config.idle_timeout
@@ -389,9 +244,9 @@ module ActiveRecord
389
244
  # then that +thread+ does indeed own that +conn+. However, an absence of such
390
245
  # mapping does not mean that the +thread+ doesn't own the said connection. In
391
246
  # that case +conn.owner+ attr should be consulted.
392
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
247
+ # Access and modification of <tt>@leases</tt> does not require
393
248
  # synchronization.
394
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
249
+ @leases = LeaseRegistry.new
395
250
 
396
251
  @connections = []
397
252
  @automatic_reconnect = true
@@ -404,69 +259,176 @@ module ActiveRecord
404
259
  @threads_blocking_new_connections = 0
405
260
 
406
261
  @available = ConnectionLeasingQueue.new self
262
+ @pinned_connection = nil
263
+ @pinned_connections_depth = 0
264
+
265
+ @async_executor = build_async_executor
407
266
 
408
- @lock_thread = false
267
+ @schema_cache = nil
409
268
 
410
269
  @reaper = Reaper.new(self, db_config.reaping_frequency)
411
270
  @reaper.run
412
271
  end
413
272
 
414
- def lock_thread=(lock_thread)
415
- if lock_thread
416
- @lock_thread = Thread.current
417
- else
418
- @lock_thread = nil
419
- end
273
+ def inspect # :nodoc:
274
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
275
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
276
+
277
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
278
+ end
279
+
280
+ def schema_cache
281
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
282
+ end
283
+
284
+ def schema_reflection=(schema_reflection)
285
+ pool_config.schema_reflection = schema_reflection
286
+ @schema_cache = nil
287
+ end
288
+
289
+ def migration_context # :nodoc:
290
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
291
+ end
292
+
293
+ def migrations_paths # :nodoc:
294
+ db_config.migrations_paths || Migrator.migrations_paths
295
+ end
296
+
297
+ def schema_migration # :nodoc:
298
+ SchemaMigration.new(self)
299
+ end
300
+
301
+ def internal_metadata # :nodoc:
302
+ InternalMetadata.new(self)
420
303
  end
421
304
 
422
305
  # Retrieve the connection associated with the current thread, or call
423
306
  # #checkout to obtain one if necessary.
424
307
  #
425
- # #connection can be called any number of times; the connection is
308
+ # #lease_connection can be called any number of times; the connection is
426
309
  # held in a cache keyed by a thread.
310
+ def lease_connection
311
+ lease = connection_lease
312
+ lease.sticky = true
313
+ lease.connection ||= checkout
314
+ end
315
+
316
+ def permanent_lease? # :nodoc:
317
+ connection_lease.sticky.nil?
318
+ end
319
+
427
320
  def connection
428
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
321
+ ActiveRecord.deprecator.warn(<<~MSG)
322
+ ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
323
+ and will be removed in Rails 8.0. Use #lease_connection instead.
324
+ MSG
325
+ lease_connection
326
+ end
327
+
328
+ def pin_connection!(lock_thread) # :nodoc:
329
+ @pinned_connection ||= (connection_lease&.connection || checkout)
330
+ @pinned_connections_depth += 1
331
+
332
+ # Any leased connection must be in @connections otherwise
333
+ # some methods like #connected? won't behave correctly
334
+ unless @connections.include?(@pinned_connection)
335
+ @connections << @pinned_connection
336
+ end
337
+
338
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
339
+ @pinned_connection.verify! # eagerly validate the connection
340
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
341
+ end
342
+
343
+ def unpin_connection! # :nodoc:
344
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
345
+
346
+ clean = true
347
+ @pinned_connection.lock.synchronize do
348
+ @pinned_connections_depth -= 1
349
+ connection = @pinned_connection
350
+ @pinned_connection = nil if @pinned_connections_depth.zero?
351
+
352
+ if connection.transaction_open?
353
+ connection.rollback_transaction
354
+ else
355
+ # Something committed or rolled back the transaction
356
+ clean = false
357
+ connection.reset!
358
+ end
359
+
360
+ if @pinned_connection.nil?
361
+ connection.steal!
362
+ connection.lock_thread = nil
363
+ checkin(connection)
364
+ end
365
+ end
366
+
367
+ clean
368
+ end
369
+
370
+ def connection_class # :nodoc:
371
+ pool_config.connection_class
429
372
  end
430
373
 
431
374
  # Returns true if there is an open connection being used for the current thread.
432
375
  #
433
376
  # This method only works for connections that have been obtained through
434
- # #connection or #with_connection methods. Connections obtained through
377
+ # #lease_connection or #with_connection methods. Connections obtained through
435
378
  # #checkout will not be detected by #active_connection?
436
379
  def active_connection?
437
- @thread_cached_conns[connection_cache_key(current_thread)]
380
+ connection_lease.connection
438
381
  end
382
+ alias_method :active_connection, :active_connection? # :nodoc:
439
383
 
440
384
  # Signal that the thread is finished with the current connection.
441
385
  # #release_connection releases the connection-thread association
442
386
  # and returns the connection to the pool.
443
387
  #
444
388
  # This method only works for connections that have been obtained through
445
- # #connection or #with_connection methods, connections obtained through
389
+ # #lease_connection or #with_connection methods, connections obtained through
446
390
  # #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))
391
+ def release_connection(existing_lease = nil)
392
+ if conn = connection_lease.release
449
393
  checkin conn
394
+ return true
395
+ end
396
+ false
397
+ end
398
+
399
+ # Yields a connection from the connection pool to the block. If no connection
400
+ # is already checked out by the current thread, a connection will be checked
401
+ # out from the pool, yielded to the block, and then returned to the pool when
402
+ # the block is finished. If a connection has already been checked out on the
403
+ # current thread, such as via #lease_connection or #with_connection, that existing
404
+ # connection will be the one yielded and it will not be returned to the pool
405
+ # automatically at the end of the block; it is expected that such an existing
406
+ # connection will be properly returned to the pool by the code that checked
407
+ # it out.
408
+ def with_connection(prevent_permanent_checkout: false)
409
+ lease = connection_lease
410
+ sticky_was = lease.sticky
411
+ lease.sticky = false if prevent_permanent_checkout
412
+
413
+ if lease.connection
414
+ begin
415
+ yield lease.connection
416
+ ensure
417
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
418
+ end
419
+ else
420
+ begin
421
+ yield lease.connection = checkout
422
+ ensure
423
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
424
+ release_connection(lease) unless lease.sticky
425
+ end
450
426
  end
451
427
  end
452
428
 
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
429
  # Returns true if a connection has already been opened.
468
430
  def connected?
469
- synchronize { @connections.any? }
431
+ synchronize { @connections.any?(&:connected?) }
470
432
  end
471
433
 
472
434
  # Returns an array containing the connections currently in the pool.
@@ -501,6 +463,7 @@ module ActiveRecord
501
463
  conn.disconnect!
502
464
  end
503
465
  @connections = []
466
+ @leases.clear
504
467
  @available.clear
505
468
  end
506
469
  end
@@ -527,7 +490,7 @@ module ActiveRecord
527
490
  @connections.each do |conn|
528
491
  conn.discard!
529
492
  end
530
- @connections = @available = @thread_cached_conns = nil
493
+ @connections = @available = @leases = nil
531
494
  end
532
495
  end
533
496
 
@@ -585,7 +548,21 @@ module ActiveRecord
585
548
  # Raises:
586
549
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
587
550
  def checkout(checkout_timeout = @checkout_timeout)
588
- checkout_and_verify(acquire_connection(checkout_timeout))
551
+ if @pinned_connection
552
+ @pinned_connection.lock.synchronize do
553
+ synchronize do
554
+ @pinned_connection.verify!
555
+ # Any leased connection must be in @connections otherwise
556
+ # some methods like #connected? won't behave correctly
557
+ unless @connections.include?(@pinned_connection)
558
+ @connections << @pinned_connection
559
+ end
560
+ end
561
+ end
562
+ @pinned_connection
563
+ else
564
+ checkout_and_verify(acquire_connection(checkout_timeout))
565
+ end
589
566
  end
590
567
 
591
568
  # Check-in a database connection back into the pool, indicating that you
@@ -594,9 +571,11 @@ module ActiveRecord
594
571
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
595
572
  # calling #checkout on this pool.
596
573
  def checkin(conn)
574
+ return if @pinned_connection.equal?(conn)
575
+
597
576
  conn.lock.synchronize do
598
577
  synchronize do
599
- remove_connection_from_thread_cache conn
578
+ connection_lease.clear(conn)
600
579
 
601
580
  conn._run_checkin_callbacks do
602
581
  conn.expire
@@ -695,8 +674,7 @@ module ActiveRecord
695
674
  @available.num_waiting
696
675
  end
697
676
 
698
- # Return connection pool's usage statistic
699
- # Example:
677
+ # Returns the connection pool's usage statistic.
700
678
  #
701
679
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
702
680
  def stat
@@ -713,7 +691,32 @@ module ActiveRecord
713
691
  end
714
692
  end
715
693
 
694
+ def schedule_query(future_result) # :nodoc:
695
+ @async_executor.post { future_result.execute_or_skip }
696
+ Thread.pass
697
+ end
698
+
716
699
  private
700
+ def connection_lease
701
+ @leases[ActiveSupport::IsolatedExecutionState.context]
702
+ end
703
+
704
+ def build_async_executor
705
+ case ActiveRecord.async_query_executor
706
+ when :multi_thread_pool
707
+ if @db_config.max_threads > 0
708
+ Concurrent::ThreadPoolExecutor.new(
709
+ min_threads: @db_config.min_threads,
710
+ max_threads: @db_config.max_threads,
711
+ max_queue: @db_config.max_queue,
712
+ fallback_policy: :caller_runs
713
+ )
714
+ end
715
+ when :global_thread_pool
716
+ ActiveRecord.global_thread_pool_async_query_executor
717
+ end
718
+ end
719
+
717
720
  #--
718
721
  # this is unfortunately not concurrent
719
722
  def bulk_make_new_connections(num_new_conns_needed)
@@ -726,19 +729,6 @@ module ActiveRecord
726
729
  end
727
730
  end
728
731
 
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
732
  # Take control of all existing connections so a "group" action such as
743
733
  # reload/disconnect can be performed safely. It is no longer enough to
744
734
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -752,18 +742,21 @@ module ActiveRecord
752
742
 
753
743
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
754
744
  collected_conns = synchronize do
745
+ reap # No need to wait for dead owners
746
+
755
747
  # account for our own connections
756
- @connections.select { |conn| conn.owner == Thread.current }
748
+ @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
757
749
  end
758
750
 
759
751
  newly_checked_out = []
760
- timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
752
+ timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
761
753
 
762
- @available.with_a_bias_for(Thread.current) do
754
+ @available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
763
755
  loop do
764
756
  synchronize do
765
757
  return if collected_conns.size == @connections.size && @now_connecting == 0
766
- remaining_timeout = timeout_time - Concurrent.monotonic_time
758
+
759
+ remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
767
760
  remaining_timeout = 0 if remaining_timeout < 0
768
761
  conn = checkout_for_exclusive_access(remaining_timeout)
769
762
  collected_conns << conn
@@ -806,14 +799,14 @@ module ActiveRecord
806
799
 
807
800
  thread_report = []
808
801
  @connections.each do |conn|
809
- unless conn.owner == Thread.current
802
+ unless conn.owner == ActiveSupport::IsolatedExecutionState.context
810
803
  thread_report << "#{conn} is owned by #{conn.owner}"
811
804
  end
812
805
  end
813
806
 
814
807
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
815
808
 
816
- raise ExclusiveConnectionTimeoutError, msg
809
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
817
810
  end
818
811
 
819
812
  def with_new_connections_blocked
@@ -867,21 +860,31 @@ module ActiveRecord
867
860
  conn
868
861
  else
869
862
  reap
870
- @available.poll(checkout_timeout)
863
+ # Retry after reaping, which may return an available connection,
864
+ # remove an inactive connection, or both
865
+ if conn = @available.poll || try_to_checkout_new_connection
866
+ conn
867
+ else
868
+ @available.poll(checkout_timeout)
869
+ end
871
870
  end
871
+ rescue ConnectionTimeoutError => ex
872
+ raise ex.set_pool(self)
872
873
  end
873
874
 
874
875
  #--
875
876
  # if owner_thread param is omitted, this must be called in synchronize block
876
877
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
877
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
878
+ @leases[owner_thread].clear(conn)
878
879
  end
879
880
  alias_method :release, :remove_connection_from_thread_cache
880
881
 
881
882
  def new_connection
882
- Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
883
- conn.check_version
884
- end
883
+ connection = db_config.new_connection
884
+ connection.pool = self
885
+ connection
886
+ rescue ConnectionNotEstablished => ex
887
+ raise ex.set_pool(self)
885
888
  end
886
889
 
887
890
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
@@ -919,6 +922,12 @@ module ActiveRecord
919
922
  def adopt_connection(conn)
920
923
  conn.pool = self
921
924
  @connections << conn
925
+
926
+ # We just created the first connection, it's time to load the schema
927
+ # cache if that wasn't eagerly done before
928
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
929
+ schema_cache.load!
930
+ end
922
931
  end
923
932
 
924
933
  def checkout_new_connection
@@ -928,302 +937,14 @@ module ActiveRecord
928
937
 
929
938
  def checkout_and_verify(c)
930
939
  c._run_checkout_callbacks do
931
- c.verify!
940
+ c.clean!
932
941
  end
933
942
  c
934
- rescue
943
+ rescue Exception
935
944
  remove c
936
945
  c.disconnect!
937
946
  raise
938
947
  end
939
948
  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
949
  end
1229
950
  end