activerecord 6.0.0 → 7.2.3

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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  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 +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -3,43 +3,57 @@
3
3
  require "thread"
4
4
  require "concurrent/map"
5
5
  require "monitor"
6
- require "weakref"
7
6
 
8
- module ActiveRecord
9
- # Raised when a connection could not be obtained within the connection
10
- # acquisition timeout period: because max connections in pool
11
- # are in use.
12
- class ConnectionTimeoutError < ConnectionNotEstablished
13
- end
14
-
15
- # Raised when a pool was unable to get ahold of all its connections
16
- # to perform a "group" action such as
17
- # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
18
- # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
19
- class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
20
- end
7
+ require "active_record/connection_adapters/abstract/connection_pool/queue"
8
+ require "active_record/connection_adapters/abstract/connection_pool/reaper"
21
9
 
10
+ module ActiveRecord
22
11
  module ConnectionAdapters
23
12
  module AbstractPool # :nodoc:
24
- def get_schema_cache(connection)
25
- @schema_cache ||= SchemaCache.new(connection)
26
- @schema_cache.connection = connection
27
- @schema_cache
28
- end
29
-
30
- def set_schema_cache(cache)
31
- @schema_cache = cache
32
- end
33
13
  end
34
14
 
35
15
  class NullPool # :nodoc:
36
16
  include ConnectionAdapters::AbstractPool
37
17
 
18
+ class NullConfig # :nodoc:
19
+ def method_missing(...)
20
+ nil
21
+ end
22
+ end
23
+ NULL_CONFIG = NullConfig.new # :nodoc:
24
+
38
25
  def initialize
39
- @schema_cache = nil
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 query_cache; end
42
+ def checkin(_); end
43
+ def remove(_); end
44
+ def async_executor; end
45
+
46
+ def db_config
47
+ NULL_CONFIG
48
+ end
49
+
50
+ def dirties_query_cache
51
+ true
40
52
  end
41
53
  end
42
54
 
55
+ # = Active Record Connection Pool
56
+ #
43
57
  # Connection pool base class for managing Active Record database
44
58
  # connections.
45
59
  #
@@ -54,19 +68,17 @@ module ActiveRecord
54
68
  # handle cases in which there are more threads than connections: if all
55
69
  # connections have been checked out, and a thread tries to checkout a
56
70
  # connection anyway, then ConnectionPool will wait until some other thread
57
- # has checked in a connection.
71
+ # has checked in a connection, or the +checkout_timeout+ has expired.
58
72
  #
59
73
  # == Obtaining (checking out) a connection
60
74
  #
61
75
  # Connections can be obtained and used from a connection pool in several
62
76
  # ways:
63
77
  #
64
- # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
65
- # as with Active Record 2.1 and
66
- # earlier (pre-connection-pooling). Eventually, when you're done with
67
- # the connection(s) and wish it to be returned to the pool, you call
68
- # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
69
- # This will be the default behavior for Active Record when used in conjunction with
78
+ # 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection].
79
+ # When you're done with the connection(s) and wish it to be returned to the pool, you call
80
+ # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
81
+ # This is the default behavior for Active Record when used in conjunction with
70
82
  # Action Pack's request handling cycle.
71
83
  # 2. Manually check out a connection from the pool with
72
84
  # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
@@ -79,6 +91,12 @@ module ActiveRecord
79
91
  # Connections in the pool are actually AbstractAdapter objects (or objects
80
92
  # compatible with AbstractAdapter's interface).
81
93
  #
94
+ # While a thread has a connection checked out from the pool using one of the
95
+ # above three methods, that connection will automatically be the one used
96
+ # by ActiveRecord queries executing on that thread. It is not required to
97
+ # explicitly pass the checked out connection to \Rails models or queries, for
98
+ # example.
99
+ #
82
100
  # == Options
83
101
  #
84
102
  # There are several connection-pooling-related options that you can add to
@@ -101,297 +119,141 @@ module ActiveRecord
101
119
  # * private methods that require being called in a +synchronize+ blocks
102
120
  # are now explicitly documented
103
121
  class ConnectionPool
104
- # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
105
- # with which it shares a Monitor.
106
- class Queue
107
- def initialize(lock = Monitor.new)
108
- @lock = lock
109
- @cond = @lock.new_cond
110
- @num_waiting = 0
111
- @queue = []
112
- end
113
-
114
- # Test if any threads are currently waiting on the queue.
115
- def any_waiting?
116
- synchronize do
117
- @num_waiting > 0
122
+ # Prior to 3.3.5, WeakKeyMap had a use after free bug
123
+ # https://bugs.ruby-lang.org/issues/20688
124
+ if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
125
+ WeakThreadKeyMap = ObjectSpace::WeakKeyMap
126
+ else
127
+ class WeakThreadKeyMap # :nodoc:
128
+ # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
129
+ # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
130
+ def initialize
131
+ @map = {}
118
132
  end
119
- end
120
133
 
121
- # Returns the number of threads currently waiting on this
122
- # queue.
123
- def num_waiting
124
- synchronize do
125
- @num_waiting
134
+ def clear
135
+ @map.clear
126
136
  end
127
- end
128
137
 
129
- # Add +element+ to the queue. Never blocks.
130
- def add(element)
131
- synchronize do
132
- @queue.push element
133
- @cond.signal
138
+ def [](key)
139
+ @map[key]
134
140
  end
135
- end
136
141
 
137
- # If +element+ is in the queue, remove and return it, or +nil+.
138
- def delete(element)
139
- synchronize do
140
- @queue.delete(element)
142
+ def []=(key, value)
143
+ @map.select! { |c, _| c&.alive? }
144
+ @map[key] = value
141
145
  end
142
146
  end
147
+ end
143
148
 
144
- # Remove all elements from the queue.
145
- def clear
146
- synchronize do
147
- @queue.clear
148
- end
149
- end
149
+ class Lease # :nodoc:
150
+ attr_accessor :connection, :sticky
150
151
 
151
- # Remove the head of the queue.
152
- #
153
- # If +timeout+ is not given, remove and return the head the
154
- # queue if the number of available elements is strictly
155
- # greater than the number of threads currently waiting (that
156
- # is, don't jump ahead in line). Otherwise, return +nil+.
157
- #
158
- # If +timeout+ is given, block if there is no element
159
- # available, waiting up to +timeout+ seconds for an element to
160
- # become available.
161
- #
162
- # Raises:
163
- # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
164
- # becomes available within +timeout+ seconds,
165
- def poll(timeout = nil)
166
- synchronize { internal_poll(timeout) }
152
+ def initialize
153
+ @connection = nil
154
+ @sticky = nil
167
155
  end
168
156
 
169
- private
170
-
171
- def internal_poll(timeout)
172
- no_wait_poll || (timeout && wait_poll(timeout))
173
- end
174
-
175
- def synchronize(&block)
176
- @lock.synchronize(&block)
177
- end
178
-
179
- # Test if the queue currently contains any elements.
180
- def any?
181
- !@queue.empty?
182
- end
183
-
184
- # A thread can remove an element from the queue without
185
- # waiting if and only if the number of currently available
186
- # connections is strictly greater than the number of waiting
187
- # threads.
188
- def can_remove_no_wait?
189
- @queue.size > @num_waiting
190
- end
191
-
192
- # Removes and returns the head of the queue if possible, or +nil+.
193
- def remove
194
- @queue.pop
195
- end
196
-
197
- # Remove and return the head the queue if the number of
198
- # available elements is strictly greater than the number of
199
- # threads currently waiting. Otherwise, return +nil+.
200
- def no_wait_poll
201
- remove if can_remove_no_wait?
202
- end
203
-
204
- # Waits on the queue up to +timeout+ seconds, then removes and
205
- # returns the head of the queue.
206
- def wait_poll(timeout)
207
- @num_waiting += 1
208
-
209
- t0 = Concurrent.monotonic_time
210
- elapsed = 0
211
- loop do
212
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
213
- @cond.wait(timeout - elapsed)
214
- end
215
-
216
- return remove if any?
157
+ def release
158
+ conn = @connection
159
+ @connection = nil
160
+ @sticky = nil
161
+ conn
162
+ end
217
163
 
218
- elapsed = Concurrent.monotonic_time - t0
219
- if elapsed >= timeout
220
- msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
221
- [timeout, elapsed]
222
- raise ConnectionTimeoutError, msg
223
- end
224
- end
225
- ensure
226
- @num_waiting -= 1
164
+ def clear(connection)
165
+ if @connection == connection
166
+ @connection = nil
167
+ @sticky = nil
168
+ true
169
+ else
170
+ false
227
171
  end
172
+ end
228
173
  end
229
174
 
230
- # Adds the ability to turn a basic fair FIFO queue into one
231
- # biased to some thread.
232
- module BiasableQueue # :nodoc:
233
- class BiasedConditionVariable # :nodoc:
234
- # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
235
- # +signal+ and +wait+ methods are only called while holding a lock
236
- def initialize(lock, other_cond, preferred_thread)
237
- @real_cond = lock.new_cond
238
- @other_cond = other_cond
239
- @preferred_thread = preferred_thread
240
- @num_waiting_on_real_cond = 0
241
- end
242
-
243
- def broadcast
244
- broadcast_on_biased
245
- @other_cond.broadcast
246
- end
247
-
248
- def broadcast_on_biased
249
- @num_waiting_on_real_cond = 0
250
- @real_cond.broadcast
251
- end
252
-
253
- def signal
254
- if @num_waiting_on_real_cond > 0
255
- @num_waiting_on_real_cond -= 1
256
- @real_cond
257
- else
258
- @other_cond
259
- end.signal
260
- end
261
-
262
- def wait(timeout)
263
- if Thread.current == @preferred_thread
264
- @num_waiting_on_real_cond += 1
265
- @real_cond
266
- else
267
- @other_cond
268
- end.wait(timeout)
269
- end
175
+ class LeaseRegistry # :nodoc:
176
+ def initialize
177
+ @mutex = Mutex.new
178
+ @map = WeakThreadKeyMap.new
270
179
  end
271
180
 
272
- def with_a_bias_for(thread)
273
- previous_cond = nil
274
- new_cond = nil
275
- synchronize do
276
- previous_cond = @cond
277
- @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
278
- end
279
- yield
280
- ensure
281
- synchronize do
282
- @cond = previous_cond if previous_cond
283
- new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
181
+ def [](context)
182
+ @mutex.synchronize do
183
+ @map[context] ||= Lease.new
284
184
  end
285
185
  end
286
- end
287
186
 
288
- # Connections must be leased while holding the main pool mutex. This is
289
- # an internal subclass that also +.leases+ returned connections while
290
- # still in queue's critical section (queue synchronizes with the same
291
- # <tt>@lock</tt> as the main pool) so that a returned connection is already
292
- # leased and there is no need to re-enter synchronized block.
293
- class ConnectionLeasingQueue < Queue # :nodoc:
294
- include BiasableQueue
295
-
296
- private
297
- def internal_poll(timeout)
298
- conn = super
299
- conn.lease if conn
300
- conn
187
+ def clear
188
+ @mutex.synchronize do
189
+ @map.clear
301
190
  end
302
- end
303
-
304
- # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
305
- # +pool+. A reaper instantiated with a zero frequency will never reap
306
- # the connection pool.
307
- #
308
- # Configure the frequency by setting +reaping_frequency+ in your database
309
- # yaml file (default 60 seconds).
310
- class Reaper
311
- attr_reader :pool, :frequency
312
-
313
- def initialize(pool, frequency)
314
- @pool = pool
315
- @frequency = frequency
316
191
  end
192
+ end
317
193
 
318
- @mutex = Mutex.new
319
- @pools = {}
320
-
194
+ module ExecutorHooks # :nodoc:
321
195
  class << self
322
- def register_pool(pool, frequency) # :nodoc:
323
- @mutex.synchronize do
324
- unless @pools.key?(frequency)
325
- @pools[frequency] = []
326
- spawn_thread(frequency)
327
- end
328
- @pools[frequency] << WeakRef.new(pool)
329
- end
196
+ def run
197
+ # noop
330
198
  end
331
199
 
332
- private
333
-
334
- def spawn_thread(frequency)
335
- Thread.new(frequency) do |t|
336
- loop do
337
- sleep t
338
- @mutex.synchronize do
339
- @pools[frequency].select!(&:weakref_alive?)
340
- @pools[frequency].each do |p|
341
- p.reap
342
- p.flush
343
- rescue WeakRef::RefError
344
- end
345
- end
200
+ def complete(_)
201
+ ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
202
+ if (connection = pool.active_connection?)
203
+ transaction = connection.current_transaction
204
+ if transaction.closed? || !transaction.joinable?
205
+ pool.release_connection
346
206
  end
347
207
  end
348
208
  end
209
+ end
349
210
  end
211
+ end
350
212
 
351
- def run
352
- return unless frequency && frequency > 0
353
- self.class.register_pool(pool, frequency)
213
+ class << self
214
+ def install_executor_hooks(executor = ActiveSupport::Executor)
215
+ executor.register_hook(ExecutorHooks)
354
216
  end
355
217
  end
356
218
 
357
219
  include MonitorMixin
358
- include QueryCache::ConnectionPoolConfiguration
220
+ prepend QueryCache::ConnectionPoolConfiguration
359
221
  include ConnectionAdapters::AbstractPool
360
222
 
361
- attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
362
- attr_reader :spec, :size, :reaper
223
+ attr_accessor :automatic_reconnect, :checkout_timeout
224
+ attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
225
+
226
+ delegate :schema_reflection, :server_version, to: :pool_config
363
227
 
364
- # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
228
+ # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
365
229
  # object which describes database connection information (e.g. adapter,
366
230
  # host name, username, password, etc), as well as the maximum size for
367
231
  # this ConnectionPool.
368
232
  #
369
233
  # The default ConnectionPool maximum size is 5.
370
- def initialize(spec)
234
+ def initialize(pool_config)
371
235
  super()
372
236
 
373
- @spec = spec
374
-
375
- @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
376
- if @idle_timeout = spec.config.fetch(:idle_timeout, 300)
377
- @idle_timeout = @idle_timeout.to_f
378
- @idle_timeout = nil if @idle_timeout <= 0
379
- end
237
+ @pool_config = pool_config
238
+ @db_config = pool_config.db_config
239
+ @role = pool_config.role
240
+ @shard = pool_config.shard
380
241
 
381
- # default max pool size to 5
382
- @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
242
+ @checkout_timeout = db_config.checkout_timeout
243
+ @idle_timeout = db_config.idle_timeout
244
+ @size = db_config.pool
383
245
 
384
246
  # This variable tracks the cache of threads mapped to reserved connections, with the
385
247
  # sole purpose of speeding up the +connection+ method. It is not the authoritative
386
248
  # registry of which thread owns which connection. Connection ownership is tracked by
387
249
  # the +connection.owner+ attr on each +connection+ instance.
388
250
  # The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
389
- # then that +thread+ does indeed own that +conn+. However, an absence of a such
251
+ # then that +thread+ does indeed own that +conn+. However, an absence of such
390
252
  # mapping does not mean that the +thread+ doesn't own the said connection. In
391
253
  # that case +conn.owner+ attr should be consulted.
392
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
254
+ # Access and modification of <tt>@leases</tt> does not require
393
255
  # synchronization.
394
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
256
+ @leases = LeaseRegistry.new
395
257
 
396
258
  @connections = []
397
259
  @automatic_reconnect = true
@@ -404,72 +266,179 @@ module ActiveRecord
404
266
  @threads_blocking_new_connections = 0
405
267
 
406
268
  @available = ConnectionLeasingQueue.new self
269
+ @pinned_connection = nil
270
+ @pinned_connections_depth = 0
407
271
 
408
- @lock_thread = false
272
+ @async_executor = build_async_executor
409
273
 
410
- # +reaping_frequency+ is configurable mostly for historical reasons, but it could
411
- # also be useful if someone wants a very low +idle_timeout+.
412
- reaping_frequency = spec.config.fetch(:reaping_frequency, 60)
413
- @reaper = Reaper.new(self, reaping_frequency && reaping_frequency.to_f)
274
+ @schema_cache = nil
275
+
276
+ @reaper = Reaper.new(self, db_config.reaping_frequency)
414
277
  @reaper.run
415
278
  end
416
279
 
417
- def lock_thread=(lock_thread)
418
- if lock_thread
419
- @lock_thread = Thread.current
420
- else
421
- @lock_thread = nil
422
- end
280
+ def inspect # :nodoc:
281
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
282
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
283
+
284
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
285
+ end
286
+
287
+ def schema_cache
288
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
289
+ end
290
+
291
+ def schema_reflection=(schema_reflection)
292
+ pool_config.schema_reflection = schema_reflection
293
+ @schema_cache = nil
294
+ end
295
+
296
+ def migration_context # :nodoc:
297
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
298
+ end
299
+
300
+ def migrations_paths # :nodoc:
301
+ db_config.migrations_paths || Migrator.migrations_paths
302
+ end
303
+
304
+ def schema_migration # :nodoc:
305
+ SchemaMigration.new(self)
306
+ end
307
+
308
+ def internal_metadata # :nodoc:
309
+ InternalMetadata.new(self)
423
310
  end
424
311
 
425
312
  # Retrieve the connection associated with the current thread, or call
426
313
  # #checkout to obtain one if necessary.
427
314
  #
428
- # #connection can be called any number of times; the connection is
315
+ # #lease_connection can be called any number of times; the connection is
429
316
  # held in a cache keyed by a thread.
317
+ def lease_connection
318
+ lease = connection_lease
319
+ lease.connection ||= checkout
320
+ lease.sticky = true
321
+ lease.connection
322
+ end
323
+
324
+ def permanent_lease? # :nodoc:
325
+ connection_lease.sticky.nil?
326
+ end
327
+
430
328
  def connection
431
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
329
+ ActiveRecord.deprecator.warn(<<~MSG)
330
+ ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
331
+ and will be removed in Rails 8.0. Use #lease_connection instead.
332
+ MSG
333
+ lease_connection
334
+ end
335
+
336
+ def pin_connection!(lock_thread) # :nodoc:
337
+ @pinned_connection ||= (connection_lease&.connection || checkout)
338
+ @pinned_connections_depth += 1
339
+
340
+ # Any leased connection must be in @connections otherwise
341
+ # some methods like #connected? won't behave correctly
342
+ unless @connections.include?(@pinned_connection)
343
+ @connections << @pinned_connection
344
+ end
345
+
346
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
347
+ @pinned_connection.pinned = true
348
+ @pinned_connection.verify! # eagerly validate the connection
349
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
350
+ end
351
+
352
+ def unpin_connection! # :nodoc:
353
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
354
+
355
+ clean = true
356
+ @pinned_connection.lock.synchronize do
357
+ @pinned_connections_depth -= 1
358
+ connection = @pinned_connection
359
+ @pinned_connection = nil if @pinned_connections_depth.zero?
360
+
361
+ if connection.transaction_open?
362
+ connection.rollback_transaction
363
+ else
364
+ # Something committed or rolled back the transaction
365
+ clean = false
366
+ connection.reset!
367
+ end
368
+
369
+ if @pinned_connection.nil?
370
+ connection.pinned = false
371
+ connection.steal!
372
+ connection.lock_thread = nil
373
+ checkin(connection)
374
+ end
375
+ end
376
+
377
+ clean
378
+ end
379
+
380
+ def connection_class # :nodoc:
381
+ pool_config.connection_class
432
382
  end
433
383
 
434
384
  # Returns true if there is an open connection being used for the current thread.
435
385
  #
436
386
  # This method only works for connections that have been obtained through
437
- # #connection or #with_connection methods. Connections obtained through
387
+ # #lease_connection or #with_connection methods. Connections obtained through
438
388
  # #checkout will not be detected by #active_connection?
439
389
  def active_connection?
440
- @thread_cached_conns[connection_cache_key(current_thread)]
390
+ connection_lease.connection
441
391
  end
392
+ alias_method :active_connection, :active_connection? # :nodoc:
442
393
 
443
394
  # Signal that the thread is finished with the current connection.
444
395
  # #release_connection releases the connection-thread association
445
396
  # and returns the connection to the pool.
446
397
  #
447
398
  # This method only works for connections that have been obtained through
448
- # #connection or #with_connection methods, connections obtained through
399
+ # #lease_connection or #with_connection methods, connections obtained through
449
400
  # #checkout will not be automatically released.
450
- def release_connection(owner_thread = Thread.current)
451
- if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
401
+ def release_connection(existing_lease = nil)
402
+ if conn = connection_lease.release
452
403
  checkin conn
404
+ return true
405
+ end
406
+ false
407
+ end
408
+
409
+ # Yields a connection from the connection pool to the block. If no connection
410
+ # is already checked out by the current thread, a connection will be checked
411
+ # out from the pool, yielded to the block, and then returned to the pool when
412
+ # the block is finished. If a connection has already been checked out on the
413
+ # current thread, such as via #lease_connection or #with_connection, that existing
414
+ # connection will be the one yielded and it will not be returned to the pool
415
+ # automatically at the end of the block; it is expected that such an existing
416
+ # connection will be properly returned to the pool by the code that checked
417
+ # it out.
418
+ def with_connection(prevent_permanent_checkout: false)
419
+ lease = connection_lease
420
+ sticky_was = lease.sticky
421
+ lease.sticky = false if prevent_permanent_checkout
422
+
423
+ if lease.connection
424
+ begin
425
+ yield lease.connection
426
+ ensure
427
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
428
+ end
429
+ else
430
+ begin
431
+ yield lease.connection = checkout
432
+ ensure
433
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
434
+ release_connection(lease) unless lease.sticky
435
+ end
453
436
  end
454
437
  end
455
438
 
456
- # If a connection obtained through #connection or #with_connection methods
457
- # already exists yield it to the block. If no such connection
458
- # exists checkout a connection, yield it to the block, and checkin the
459
- # connection when finished.
460
- def with_connection
461
- unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
462
- conn = connection
463
- fresh_connection = true
464
- end
465
- yield conn
466
- ensure
467
- release_connection if fresh_connection
468
- end
469
-
470
439
  # Returns true if a connection has already been opened.
471
440
  def connected?
472
- synchronize { @connections.any? }
441
+ synchronize { @connections.any?(&:connected?) }
473
442
  end
474
443
 
475
444
  # Returns an array containing the connections currently in the pool.
@@ -492,7 +461,7 @@ module ActiveRecord
492
461
  # Raises:
493
462
  # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
494
463
  # connections in the pool within a timeout interval (default duration is
495
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
464
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
496
465
  def disconnect(raise_on_acquisition_timeout = true)
497
466
  with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
498
467
  synchronize do
@@ -504,6 +473,7 @@ module ActiveRecord
504
473
  conn.disconnect!
505
474
  end
506
475
  @connections = []
476
+ @leases.clear
507
477
  @available.clear
508
478
  end
509
479
  end
@@ -513,7 +483,7 @@ module ActiveRecord
513
483
  #
514
484
  # The pool first tries to gain ownership of all connections. If unable to
515
485
  # do so within a timeout interval (default duration is
516
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully
486
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool is forcefully
517
487
  # disconnected without any regard for other connection owning threads.
518
488
  def disconnect!
519
489
  disconnect(false)
@@ -526,21 +496,25 @@ module ActiveRecord
526
496
  # See AbstractAdapter#discard!
527
497
  def discard! # :nodoc:
528
498
  synchronize do
529
- return if @connections.nil? # already discarded
499
+ return if self.discarded?
530
500
  @connections.each do |conn|
531
501
  conn.discard!
532
502
  end
533
- @connections = @available = @thread_cached_conns = nil
503
+ @connections = @available = @leases = nil
534
504
  end
535
505
  end
536
506
 
507
+ def discarded? # :nodoc:
508
+ @connections.nil?
509
+ end
510
+
537
511
  # Clears the cache which maps classes and re-connects connections that
538
512
  # require reloading.
539
513
  #
540
514
  # Raises:
541
515
  # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
542
516
  # connections in the pool within a timeout interval (default duration is
543
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
517
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
544
518
  def clear_reloadable_connections(raise_on_acquisition_timeout = true)
545
519
  with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
546
520
  synchronize do
@@ -562,7 +536,7 @@ module ActiveRecord
562
536
  #
563
537
  # The pool first tries to gain ownership of all connections. If unable to
564
538
  # do so within a timeout interval (default duration is
565
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool forcefully
539
+ # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool forcefully
566
540
  # clears the cache and reloads connections without any regard for other
567
541
  # connection owning threads.
568
542
  def clear_reloadable_connections!
@@ -584,7 +558,26 @@ module ActiveRecord
584
558
  # Raises:
585
559
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
586
560
  def checkout(checkout_timeout = @checkout_timeout)
587
- checkout_and_verify(acquire_connection(checkout_timeout))
561
+ return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
562
+
563
+ @pinned_connection.lock.synchronize do
564
+ synchronize do
565
+ # The pinned connection may have been cleaned up before we synchronized, so check if it is still present
566
+ if @pinned_connection
567
+ @pinned_connection.verify!
568
+
569
+ # Any leased connection must be in @connections otherwise
570
+ # some methods like #connected? won't behave correctly
571
+ unless @connections.include?(@pinned_connection)
572
+ @connections << @pinned_connection
573
+ end
574
+
575
+ @pinned_connection
576
+ else
577
+ checkout_and_verify(acquire_connection(checkout_timeout))
578
+ end
579
+ end
580
+ end
588
581
  end
589
582
 
590
583
  # Check-in a database connection back into the pool, indicating that you
@@ -593,9 +586,11 @@ module ActiveRecord
593
586
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
594
587
  # calling #checkout on this pool.
595
588
  def checkin(conn)
589
+ return if @pinned_connection.equal?(conn)
590
+
596
591
  conn.lock.synchronize do
597
592
  synchronize do
598
- remove_connection_from_thread_cache conn
593
+ connection_lease.clear(conn)
599
594
 
600
595
  conn._run_checkin_callbacks do
601
596
  conn.expire
@@ -642,6 +637,7 @@ module ActiveRecord
642
637
  # or a thread dies unexpectedly.
643
638
  def reap
644
639
  stale_connections = synchronize do
640
+ return if self.discarded?
645
641
  @connections.select do |conn|
646
642
  conn.in_use? && !conn.owner.alive?
647
643
  end.each do |conn|
@@ -666,6 +662,7 @@ module ActiveRecord
666
662
  return if minimum_idle.nil?
667
663
 
668
664
  idle_connections = synchronize do
665
+ return if self.discarded?
669
666
  @connections.select do |conn|
670
667
  !conn.in_use? && conn.seconds_idle >= minimum_idle
671
668
  end.each do |conn|
@@ -692,8 +689,7 @@ module ActiveRecord
692
689
  @available.num_waiting
693
690
  end
694
691
 
695
- # Return connection pool's usage statistic
696
- # Example:
692
+ # Returns the connection pool's usage statistic.
697
693
  #
698
694
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
699
695
  def stat
@@ -710,7 +706,40 @@ module ActiveRecord
710
706
  end
711
707
  end
712
708
 
709
+ def schedule_query(future_result) # :nodoc:
710
+ @async_executor.post { future_result.execute_or_skip }
711
+ Thread.pass
712
+ end
713
+
714
+ def new_connection # :nodoc:
715
+ connection = db_config.new_connection
716
+ connection.pool = self
717
+ connection
718
+ rescue ConnectionNotEstablished => ex
719
+ raise ex.set_pool(self)
720
+ end
721
+
713
722
  private
723
+ def connection_lease
724
+ @leases[ActiveSupport::IsolatedExecutionState.context]
725
+ end
726
+
727
+ def build_async_executor
728
+ case ActiveRecord.async_query_executor
729
+ when :multi_thread_pool
730
+ if @db_config.max_threads > 0
731
+ Concurrent::ThreadPoolExecutor.new(
732
+ min_threads: @db_config.min_threads,
733
+ max_threads: @db_config.max_threads,
734
+ max_queue: @db_config.max_queue,
735
+ fallback_policy: :caller_runs
736
+ )
737
+ end
738
+ when :global_thread_pool
739
+ ActiveRecord.global_thread_pool_async_query_executor
740
+ end
741
+ end
742
+
714
743
  #--
715
744
  # this is unfortunately not concurrent
716
745
  def bulk_make_new_connections(num_new_conns_needed)
@@ -723,19 +752,6 @@ module ActiveRecord
723
752
  end
724
753
  end
725
754
 
726
- #--
727
- # From the discussion on GitHub:
728
- # https://github.com/rails/rails/pull/14938#commitcomment-6601951
729
- # This hook-in method allows for easier monkey-patching fixes needed by
730
- # JRuby users that use Fibers.
731
- def connection_cache_key(thread)
732
- thread
733
- end
734
-
735
- def current_thread
736
- @lock_thread || Thread.current
737
- end
738
-
739
755
  # Take control of all existing connections so a "group" action such as
740
756
  # reload/disconnect can be performed safely. It is no longer enough to
741
757
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -749,18 +765,21 @@ module ActiveRecord
749
765
 
750
766
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
751
767
  collected_conns = synchronize do
768
+ reap # No need to wait for dead owners
769
+
752
770
  # account for our own connections
753
- @connections.select { |conn| conn.owner == Thread.current }
771
+ @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
754
772
  end
755
773
 
756
774
  newly_checked_out = []
757
- timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
775
+ timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
758
776
 
759
- @available.with_a_bias_for(Thread.current) do
777
+ @available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
760
778
  loop do
761
779
  synchronize do
762
780
  return if collected_conns.size == @connections.size && @now_connecting == 0
763
- remaining_timeout = timeout_time - Concurrent.monotonic_time
781
+
782
+ remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
764
783
  remaining_timeout = 0 if remaining_timeout < 0
765
784
  conn = checkout_for_exclusive_access(remaining_timeout)
766
785
  collected_conns << conn
@@ -803,14 +822,14 @@ module ActiveRecord
803
822
 
804
823
  thread_report = []
805
824
  @connections.each do |conn|
806
- unless conn.owner == Thread.current
825
+ unless conn.owner == ActiveSupport::IsolatedExecutionState.context
807
826
  thread_report << "#{conn} is owned by #{conn.owner}"
808
827
  end
809
828
  end
810
829
 
811
830
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
812
831
 
813
- raise ExclusiveConnectionTimeoutError, msg
832
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
814
833
  end
815
834
 
816
835
  def with_new_connections_blocked
@@ -864,22 +883,26 @@ module ActiveRecord
864
883
  conn
865
884
  else
866
885
  reap
867
- @available.poll(checkout_timeout)
886
+ # Retry after reaping, which may return an available connection,
887
+ # remove an inactive connection, or both
888
+ if conn = @available.poll || try_to_checkout_new_connection
889
+ conn
890
+ else
891
+ @available.poll(checkout_timeout)
892
+ end
868
893
  end
894
+ rescue ConnectionTimeoutError => ex
895
+ raise ex.set_pool(self)
869
896
  end
870
897
 
871
898
  #--
872
899
  # if owner_thread param is omitted, this must be called in synchronize block
873
900
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
874
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
875
- end
876
- alias_method :release, :remove_connection_from_thread_cache
877
-
878
- def new_connection
879
- Base.send(spec.adapter_method, spec.config).tap do |conn|
880
- conn.check_version
901
+ if owner_thread
902
+ @leases[owner_thread].clear(conn)
881
903
  end
882
904
  end
905
+ alias_method :release, :remove_connection_from_thread_cache
883
906
 
884
907
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
885
908
  # to the DB is done outside main synchronized section.
@@ -916,6 +939,12 @@ module ActiveRecord
916
939
  def adopt_connection(conn)
917
940
  conn.pool = self
918
941
  @connections << conn
942
+
943
+ # We just created the first connection, it's time to load the schema
944
+ # cache if that wasn't eagerly done before
945
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
946
+ schema_cache.load!
947
+ end
919
948
  end
920
949
 
921
950
  def checkout_new_connection
@@ -925,241 +954,14 @@ module ActiveRecord
925
954
 
926
955
  def checkout_and_verify(c)
927
956
  c._run_checkout_callbacks do
928
- c.verify!
957
+ c.clean!
929
958
  end
930
959
  c
931
- rescue
960
+ rescue Exception
932
961
  remove c
933
962
  c.disconnect!
934
963
  raise
935
964
  end
936
965
  end
937
-
938
- # ConnectionHandler is a collection of ConnectionPool objects. It is used
939
- # for keeping separate connection pools that connect to different databases.
940
- #
941
- # For example, suppose that you have 5 models, with the following hierarchy:
942
- #
943
- # class Author < ActiveRecord::Base
944
- # end
945
- #
946
- # class BankAccount < ActiveRecord::Base
947
- # end
948
- #
949
- # class Book < ActiveRecord::Base
950
- # establish_connection :library_db
951
- # end
952
- #
953
- # class ScaryBook < Book
954
- # end
955
- #
956
- # class GoodBook < Book
957
- # end
958
- #
959
- # And a database.yml that looked like this:
960
- #
961
- # development:
962
- # database: my_application
963
- # host: localhost
964
- #
965
- # library_db:
966
- # database: library
967
- # host: some.library.org
968
- #
969
- # Your primary database in the development environment is "my_application"
970
- # but the Book model connects to a separate database called "library_db"
971
- # (this can even be a database on a different machine).
972
- #
973
- # Book, ScaryBook and GoodBook will all use the same connection pool to
974
- # "library_db" while Author, BankAccount, and any other models you create
975
- # will use the default connection pool to "my_application".
976
- #
977
- # The various connection pools are managed by a single instance of
978
- # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
979
- # All Active Record models use this handler to determine the connection pool that they
980
- # should use.
981
- #
982
- # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
983
- # about the model. The model needs to pass a specification name to the handler,
984
- # in order to look up the correct connection pool.
985
- class ConnectionHandler
986
- def self.create_owner_to_pool # :nodoc:
987
- Concurrent::Map.new(initial_capacity: 2) do |h, k|
988
- # Discard the parent's connection pools immediately; we have no need
989
- # of them
990
- discard_unowned_pools(h)
991
-
992
- h[k] = Concurrent::Map.new(initial_capacity: 2)
993
- end
994
- end
995
-
996
- def self.unowned_pool_finalizer(pid_map) # :nodoc:
997
- lambda do |_|
998
- discard_unowned_pools(pid_map)
999
- end
1000
- end
1001
-
1002
- def self.discard_unowned_pools(pid_map) # :nodoc:
1003
- pid_map.each do |pid, pools|
1004
- pools.values.compact.each(&:discard!) unless pid == Process.pid
1005
- end
1006
- end
1007
-
1008
- def initialize
1009
- # These caches are keyed by spec.name (ConnectionSpecification#name).
1010
- @owner_to_pool = ConnectionHandler.create_owner_to_pool
1011
-
1012
- # Backup finalizer: if the forked child never needed a pool, the above
1013
- # early discard has not occurred
1014
- ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
1015
- end
1016
-
1017
- def prevent_writes # :nodoc:
1018
- Thread.current[:prevent_writes]
1019
- end
1020
-
1021
- def prevent_writes=(prevent_writes) # :nodoc:
1022
- Thread.current[:prevent_writes] = prevent_writes
1023
- end
1024
-
1025
- # Prevent writing to the database regardless of role.
1026
- #
1027
- # In some cases you may want to prevent writes to the database
1028
- # even if you are on a database that can write. `while_preventing_writes`
1029
- # will prevent writes to the database for the duration of the block.
1030
- def while_preventing_writes(enabled = true)
1031
- original, self.prevent_writes = self.prevent_writes, enabled
1032
- yield
1033
- ensure
1034
- self.prevent_writes = original
1035
- end
1036
-
1037
- def connection_pool_list
1038
- owner_to_pool.values.compact
1039
- end
1040
- alias :connection_pools :connection_pool_list
1041
-
1042
- def establish_connection(config)
1043
- resolver = ConnectionSpecification::Resolver.new(Base.configurations)
1044
- spec = resolver.spec(config)
1045
-
1046
- remove_connection(spec.name)
1047
-
1048
- message_bus = ActiveSupport::Notifications.instrumenter
1049
- payload = {
1050
- connection_id: object_id
1051
- }
1052
- if spec
1053
- payload[:spec_name] = spec.name
1054
- payload[:config] = spec.config
1055
- end
1056
-
1057
- message_bus.instrument("!connection.active_record", payload) do
1058
- owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
1059
- end
1060
-
1061
- owner_to_pool[spec.name]
1062
- end
1063
-
1064
- # Returns true if there are any active connections among the connection
1065
- # pools that the ConnectionHandler is managing.
1066
- def active_connections?
1067
- connection_pool_list.any?(&:active_connection?)
1068
- end
1069
-
1070
- # Returns any connections in use by the current thread back to the pool,
1071
- # and also returns connections to the pool cached by threads that are no
1072
- # longer alive.
1073
- def clear_active_connections!
1074
- connection_pool_list.each(&:release_connection)
1075
- end
1076
-
1077
- # Clears the cache which maps classes.
1078
- #
1079
- # See ConnectionPool#clear_reloadable_connections! for details.
1080
- def clear_reloadable_connections!
1081
- connection_pool_list.each(&:clear_reloadable_connections!)
1082
- end
1083
-
1084
- def clear_all_connections!
1085
- connection_pool_list.each(&:disconnect!)
1086
- end
1087
-
1088
- # Disconnects all currently idle connections.
1089
- #
1090
- # See ConnectionPool#flush! for details.
1091
- def flush_idle_connections!
1092
- connection_pool_list.each(&:flush!)
1093
- end
1094
-
1095
- # Locate the connection of the nearest super class. This can be an
1096
- # active or defined connection: if it is the latter, it will be
1097
- # opened and set as the active connection for the class it was defined
1098
- # for (not necessarily the current class).
1099
- def retrieve_connection(spec_name) #:nodoc:
1100
- pool = retrieve_connection_pool(spec_name)
1101
-
1102
- unless pool
1103
- # multiple database application
1104
- if ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
1105
- raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
1106
- else
1107
- raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found."
1108
- end
1109
- end
1110
-
1111
- pool.connection
1112
- end
1113
-
1114
- # Returns true if a connection that's accessible to this class has
1115
- # already been opened.
1116
- def connected?(spec_name)
1117
- pool = retrieve_connection_pool(spec_name)
1118
- pool && pool.connected?
1119
- end
1120
-
1121
- # Remove the connection for this class. This will close the active
1122
- # connection and the defined connection (if they exist). The result
1123
- # can be used as an argument for #establish_connection, for easily
1124
- # re-establishing the connection.
1125
- def remove_connection(spec_name)
1126
- if pool = owner_to_pool.delete(spec_name)
1127
- pool.automatic_reconnect = false
1128
- pool.disconnect!
1129
- pool.spec.config
1130
- end
1131
- end
1132
-
1133
- # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
1134
- # This makes retrieving the connection pool O(1) once the process is warm.
1135
- # When a connection is established or removed, we invalidate the cache.
1136
- def retrieve_connection_pool(spec_name)
1137
- owner_to_pool.fetch(spec_name) do
1138
- # Check if a connection was previously established in an ancestor process,
1139
- # which may have been forked.
1140
- if ancestor_pool = pool_from_any_process_for(spec_name)
1141
- # A connection was established in an ancestor process that must have
1142
- # subsequently forked. We can't reuse the connection, but we can copy
1143
- # the specification and establish a new connection with it.
1144
- establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
1145
- pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
1146
- end
1147
- else
1148
- owner_to_pool[spec_name] = nil
1149
- end
1150
- end
1151
- end
1152
-
1153
- private
1154
-
1155
- def owner_to_pool
1156
- @owner_to_pool[Process.pid]
1157
- end
1158
-
1159
- def pool_from_any_process_for(spec_name)
1160
- owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
1161
- owner_to_pool && owner_to_pool[spec_name]
1162
- end
1163
- end
1164
966
  end
1165
967
  end