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
@@ -0,0 +1,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "concurrent/map"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ # = Active Record Connection Handler
9
+ #
10
+ # ConnectionHandler is a collection of ConnectionPool objects. It is used
11
+ # for keeping separate connection pools that connect to different databases.
12
+ #
13
+ # For example, suppose that you have 5 models, with the following hierarchy:
14
+ #
15
+ # class Author < ActiveRecord::Base
16
+ # end
17
+ #
18
+ # class BankAccount < ActiveRecord::Base
19
+ # end
20
+ #
21
+ # class Book < ActiveRecord::Base
22
+ # establish_connection :library_db
23
+ # end
24
+ #
25
+ # class ScaryBook < Book
26
+ # end
27
+ #
28
+ # class GoodBook < Book
29
+ # end
30
+ #
31
+ # And a database.yml that looked like this:
32
+ #
33
+ # development:
34
+ # database: my_application
35
+ # host: localhost
36
+ #
37
+ # library_db:
38
+ # database: library
39
+ # host: some.library.org
40
+ #
41
+ # Your primary database in the development environment is "my_application"
42
+ # but the Book model connects to a separate database called "library_db"
43
+ # (this can even be a database on a different machine).
44
+ #
45
+ # Book, ScaryBook, and GoodBook will all use the same connection pool to
46
+ # "library_db" while Author, BankAccount, and any other models you create
47
+ # will use the default connection pool to "my_application".
48
+ #
49
+ # The various connection pools are managed by a single instance of
50
+ # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
51
+ # All Active Record models use this handler to determine the connection pool that they
52
+ # should use.
53
+ #
54
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
55
+ # about the model. The model needs to pass a connection specification name to the handler,
56
+ # in order to look up the correct connection pool.
57
+ class ConnectionHandler
58
+ class StringConnectionName # :nodoc:
59
+ attr_reader :name
60
+
61
+ def initialize(name)
62
+ @name = name
63
+ end
64
+
65
+ def primary_class?
66
+ false
67
+ end
68
+
69
+ def current_preventing_writes
70
+ false
71
+ end
72
+ end
73
+
74
+ def initialize
75
+ # These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
76
+ @connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
77
+ end
78
+
79
+ def prevent_writes # :nodoc:
80
+ ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes]
81
+ end
82
+
83
+ def prevent_writes=(prevent_writes) # :nodoc:
84
+ ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes
85
+ end
86
+
87
+ def connection_pool_names # :nodoc:
88
+ connection_name_to_pool_manager.keys
89
+ end
90
+
91
+ # Returns the pools for a connection handler and given role. If +:all+ is passed,
92
+ # all pools belonging to the connection handler will be returned.
93
+ def connection_pool_list(role = nil)
94
+ if role.nil? || role == :all
95
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
96
+ else
97
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
98
+ end
99
+ end
100
+ alias :connection_pools :connection_pool_list
101
+
102
+ def each_connection_pool(role = nil, &block) # :nodoc:
103
+ role = nil if role == :all
104
+ return enum_for(__method__, role) unless block_given?
105
+
106
+ connection_name_to_pool_manager.each_value do |manager|
107
+ manager.each_pool_config(role) do |pool_config|
108
+ yield pool_config.pool
109
+ end
110
+ end
111
+ end
112
+
113
+ def establish_connection(config, owner_name: Base, role: Base.current_role, shard: Base.current_shard, clobber: false)
114
+ owner_name = determine_owner_name(owner_name, config)
115
+
116
+ pool_config = resolve_pool_config(config, owner_name, role, shard)
117
+ db_config = pool_config.db_config
118
+
119
+ pool_manager = set_pool_manager(pool_config.connection_name)
120
+
121
+ # If there is an existing pool with the same values as the pool_config
122
+ # don't remove the connection. Connections should only be removed if we are
123
+ # establishing a connection on a class that is already connected to a different
124
+ # configuration.
125
+ existing_pool_config = pool_manager.get_pool_config(role, shard)
126
+
127
+ if !clobber && existing_pool_config && existing_pool_config.db_config == db_config
128
+ # Update the pool_config's connection class if it differs. This is used
129
+ # for ensuring that ActiveRecord::Base and the primary_abstract_class use
130
+ # the same pool. Without this granular swapping will not work correctly.
131
+ if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
132
+ existing_pool_config.connection_class = owner_name
133
+ end
134
+
135
+ existing_pool_config.pool
136
+ else
137
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
138
+ pool_manager.set_pool_config(role, shard, pool_config)
139
+
140
+ payload = {
141
+ connection_name: pool_config.connection_name,
142
+ role: role,
143
+ shard: shard,
144
+ config: db_config.configuration_hash
145
+ }
146
+
147
+ ActiveSupport::Notifications.instrumenter.instrument("!connection.active_record", payload) do
148
+ pool_config.pool
149
+ end
150
+ end
151
+ end
152
+
153
+ # Returns true if there are any active connections among the connection
154
+ # pools that the ConnectionHandler is managing.
155
+ def active_connections?(role = nil)
156
+ each_connection_pool(role).any?(&:active_connection?)
157
+ end
158
+
159
+ # Returns any connections in use by the current thread back to the pool,
160
+ # and also returns connections to the pool cached by threads that are no
161
+ # longer alive.
162
+ def clear_active_connections!(role = nil)
163
+ each_connection_pool(role).each do |pool|
164
+ pool.release_connection
165
+ pool.disable_query_cache!
166
+ end
167
+ end
168
+
169
+ # Clears the cache which maps classes.
170
+ #
171
+ # See ConnectionPool#clear_reloadable_connections! for details.
172
+ def clear_reloadable_connections!(role = nil)
173
+ each_connection_pool(role).each(&:clear_reloadable_connections!)
174
+ end
175
+
176
+ def clear_all_connections!(role = nil)
177
+ each_connection_pool(role).each(&:disconnect!)
178
+ end
179
+
180
+ # Disconnects all currently idle connections.
181
+ #
182
+ # See ConnectionPool#flush! for details.
183
+ def flush_idle_connections!(role = nil)
184
+ each_connection_pool(role).each(&:flush!)
185
+ end
186
+
187
+ # Locate the connection of the nearest super class. This can be an
188
+ # active or defined connection: if it is the latter, it will be
189
+ # opened and set as the active connection for the class it was defined
190
+ # for (not necessarily the current class).
191
+ def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
192
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard, strict: true)
193
+ pool.lease_connection
194
+ end
195
+
196
+ # Returns true if a connection that's accessible to this class has
197
+ # already been opened.
198
+ def connected?(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
199
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
200
+ pool && pool.connected?
201
+ end
202
+
203
+ def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
204
+ if pool_manager = get_pool_manager(connection_name)
205
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
206
+ end
207
+ end
208
+
209
+ # Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
210
+ # This makes retrieving the connection pool O(1) once the process is warm.
211
+ # When a connection is established or removed, we invalidate the cache.
212
+ def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
213
+ pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
214
+
215
+ if strict && !pool
216
+ if shard != ActiveRecord::Base.default_shard
217
+ message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
218
+ elsif role != ActiveRecord::Base.default_role
219
+ message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
220
+ else
221
+ message = "No connection pool for '#{connection_name}' found."
222
+ end
223
+
224
+ raise ConnectionNotEstablished, message
225
+ end
226
+
227
+ pool
228
+ end
229
+
230
+ private
231
+ attr_reader :connection_name_to_pool_manager
232
+
233
+ # Returns the pool manager for a connection name / identifier.
234
+ def get_pool_manager(connection_name)
235
+ connection_name_to_pool_manager[connection_name]
236
+ end
237
+
238
+ # Get the existing pool manager or initialize and assign a new one.
239
+ def set_pool_manager(connection_name)
240
+ connection_name_to_pool_manager[connection_name] ||= PoolManager.new
241
+ end
242
+
243
+ def pool_managers
244
+ connection_name_to_pool_manager.values
245
+ end
246
+
247
+ def disconnect_pool_from_pool_manager(pool_manager, role, shard)
248
+ pool_config = pool_manager.remove_pool_config(role, shard)
249
+
250
+ if pool_config
251
+ pool_config.disconnect!
252
+ pool_config.db_config
253
+ end
254
+ end
255
+
256
+ # Returns an instance of PoolConfig for a given adapter.
257
+ # Accepts a hash one layer deep that contains all connection information.
258
+ #
259
+ # == Example
260
+ #
261
+ # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
262
+ # pool_config = Base.configurations.resolve_pool_config(:production)
263
+ # pool_config.db_config.configuration_hash
264
+ # # => { host: "localhost", database: "foo", adapter: "sqlite3" }
265
+ #
266
+ def resolve_pool_config(config, connection_name, role, shard)
267
+ db_config = Base.configurations.resolve(config)
268
+ db_config.validate!
269
+ raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
270
+ ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
271
+ end
272
+
273
+ def determine_owner_name(owner_name, config)
274
+ if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
275
+ StringConnectionName.new(owner_name.to_s)
276
+ elsif config.is_a?(Symbol)
277
+ StringConnectionName.new(config.to_s)
278
+ else
279
+ owner_name
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "monitor"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ class ConnectionPool
9
+ # = Active Record Connection Pool \Queue
10
+ #
11
+ # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
12
+ # with which it shares a Monitor.
13
+ class Queue
14
+ def initialize(lock = Monitor.new)
15
+ @lock = lock
16
+ @cond = @lock.new_cond
17
+ @num_waiting = 0
18
+ @queue = []
19
+ end
20
+
21
+ # Test if any threads are currently waiting on the queue.
22
+ def any_waiting?
23
+ synchronize do
24
+ @num_waiting > 0
25
+ end
26
+ end
27
+
28
+ # Returns the number of threads currently waiting on this
29
+ # queue.
30
+ def num_waiting
31
+ synchronize do
32
+ @num_waiting
33
+ end
34
+ end
35
+
36
+ # Add +element+ to the queue. Never blocks.
37
+ def add(element)
38
+ synchronize do
39
+ @queue.push element
40
+ @cond.signal
41
+ end
42
+ end
43
+
44
+ # If +element+ is in the queue, remove and return it, or +nil+.
45
+ def delete(element)
46
+ synchronize do
47
+ @queue.delete(element)
48
+ end
49
+ end
50
+
51
+ # Remove all elements from the queue.
52
+ def clear
53
+ synchronize do
54
+ @queue.clear
55
+ end
56
+ end
57
+
58
+ # Remove the head of the queue.
59
+ #
60
+ # If +timeout+ is not given, remove and return the head of the
61
+ # queue if the number of available elements is strictly
62
+ # greater than the number of threads currently waiting (that
63
+ # is, don't jump ahead in line). Otherwise, return +nil+.
64
+ #
65
+ # If +timeout+ is given, block if there is no element
66
+ # available, waiting up to +timeout+ seconds for an element to
67
+ # become available.
68
+ #
69
+ # Raises:
70
+ # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
71
+ # becomes available within +timeout+ seconds,
72
+ def poll(timeout = nil)
73
+ synchronize { internal_poll(timeout) }
74
+ end
75
+
76
+ private
77
+ def internal_poll(timeout)
78
+ no_wait_poll || (timeout && wait_poll(timeout))
79
+ end
80
+
81
+ def synchronize(&block)
82
+ @lock.synchronize(&block)
83
+ end
84
+
85
+ # Test if the queue currently contains any elements.
86
+ def any?
87
+ !@queue.empty?
88
+ end
89
+
90
+ # A thread can remove an element from the queue without
91
+ # waiting if and only if the number of currently available
92
+ # connections is strictly greater than the number of waiting
93
+ # threads.
94
+ def can_remove_no_wait?
95
+ @queue.size > @num_waiting
96
+ end
97
+
98
+ # Removes and returns the head of the queue if possible, or +nil+.
99
+ def remove
100
+ @queue.pop
101
+ end
102
+
103
+ # Remove and return the head of the queue if the number of
104
+ # available elements is strictly greater than the number of
105
+ # threads currently waiting. Otherwise, return +nil+.
106
+ def no_wait_poll
107
+ remove if can_remove_no_wait?
108
+ end
109
+
110
+ # Waits on the queue up to +timeout+ seconds, then removes and
111
+ # returns the head of the queue.
112
+ def wait_poll(timeout)
113
+ @num_waiting += 1
114
+
115
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
116
+ elapsed = 0
117
+ loop do
118
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
119
+ @cond.wait(timeout - elapsed)
120
+ end
121
+
122
+ return remove if any?
123
+
124
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
125
+ if elapsed >= timeout
126
+ msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
127
+ [timeout, elapsed]
128
+ raise ConnectionTimeoutError, msg
129
+ end
130
+ end
131
+ ensure
132
+ @num_waiting -= 1
133
+ end
134
+ end
135
+
136
+ # Adds the ability to turn a basic fair FIFO queue into one
137
+ # biased to some thread.
138
+ module BiasableQueue # :nodoc:
139
+ class BiasedConditionVariable # :nodoc:
140
+ # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
141
+ # +signal+ and +wait+ methods are only called while holding a lock
142
+ def initialize(lock, other_cond, preferred_thread)
143
+ @real_cond = lock.new_cond
144
+ @other_cond = other_cond
145
+ @preferred_thread = preferred_thread
146
+ @num_waiting_on_real_cond = 0
147
+ end
148
+
149
+ def broadcast
150
+ broadcast_on_biased
151
+ @other_cond.broadcast
152
+ end
153
+
154
+ def broadcast_on_biased
155
+ @num_waiting_on_real_cond = 0
156
+ @real_cond.broadcast
157
+ end
158
+
159
+ def signal
160
+ if @num_waiting_on_real_cond > 0
161
+ @num_waiting_on_real_cond -= 1
162
+ @real_cond
163
+ else
164
+ @other_cond
165
+ end.signal
166
+ end
167
+
168
+ def wait(timeout)
169
+ if Thread.current == @preferred_thread
170
+ @num_waiting_on_real_cond += 1
171
+ @real_cond
172
+ else
173
+ @other_cond
174
+ end.wait(timeout)
175
+ end
176
+ end
177
+
178
+ def with_a_bias_for(thread)
179
+ previous_cond = nil
180
+ new_cond = nil
181
+ synchronize do
182
+ previous_cond = @cond
183
+ @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
184
+ end
185
+ yield
186
+ ensure
187
+ synchronize do
188
+ @cond = previous_cond if previous_cond
189
+ new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
190
+ end
191
+ end
192
+ end
193
+
194
+ # Connections must be leased while holding the main pool mutex. This is
195
+ # an internal subclass that also +.leases+ returned connections while
196
+ # still in queue's critical section (queue synchronizes with the same
197
+ # <tt>@lock</tt> as the main pool) so that a returned connection is already
198
+ # leased and there is no need to re-enter synchronized block.
199
+ class ConnectionLeasingQueue < Queue # :nodoc:
200
+ include BiasableQueue
201
+
202
+ private
203
+ def internal_poll(timeout)
204
+ conn = super
205
+ conn.lease if conn
206
+ conn
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "weakref"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ class ConnectionPool
9
+ # = Active Record Connection Pool \Reaper
10
+ #
11
+ # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
12
+ # +pool+. A reaper instantiated with a zero frequency will never reap
13
+ # the connection pool.
14
+ #
15
+ # Configure the frequency by setting +reaping_frequency+ in your database
16
+ # YAML file (default 60 seconds).
17
+ class Reaper
18
+ attr_reader :pool, :frequency
19
+
20
+ def initialize(pool, frequency)
21
+ @pool = pool
22
+ @frequency = frequency
23
+ end
24
+
25
+ @mutex = Mutex.new
26
+ @pools = {}
27
+ @threads = {}
28
+
29
+ class << self
30
+ def register_pool(pool, frequency) # :nodoc:
31
+ @mutex.synchronize do
32
+ unless @threads[frequency]&.alive?
33
+ @threads[frequency] = spawn_thread(frequency)
34
+ end
35
+ @pools[frequency] ||= []
36
+ @pools[frequency] << WeakRef.new(pool)
37
+ end
38
+ end
39
+
40
+ private
41
+ def spawn_thread(frequency)
42
+ Thread.new(frequency) do |t|
43
+ # Advise multi-threaded app servers to ignore this thread for
44
+ # the purposes of fork safety warnings
45
+ Thread.current.thread_variable_set(:fork_safe, true)
46
+ Thread.current.name = "AR Pool Reaper"
47
+ running = true
48
+ while running
49
+ sleep t
50
+ @mutex.synchronize do
51
+ @pools[frequency].select! do |pool|
52
+ pool.weakref_alive? && !pool.discarded?
53
+ end
54
+
55
+ @pools[frequency].each do |p|
56
+ p.reap
57
+ p.flush
58
+ rescue WeakRef::RefError
59
+ end
60
+
61
+ if @pools[frequency].empty?
62
+ @pools.delete(frequency)
63
+ @threads.delete(frequency)
64
+ running = false
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def run
73
+ return unless frequency && frequency > 0
74
+ self.class.register_pool(pool, frequency)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end