activerecord 7.0.8.7 → 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 (283) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +781 -1777
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +30 -30
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +31 -23
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +40 -9
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +35 -21
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +4 -3
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -33
  47. data/lib/active_record/attributes.rb +96 -71
  48. data/lib/active_record/autosave_association.rb +81 -39
  49. data/lib/active_record/base.rb +11 -7
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +343 -91
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +229 -64
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +142 -12
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +539 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +289 -128
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +60 -55
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +108 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +371 -64
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +374 -203
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -45
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +51 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +298 -113
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +101 -105
  110. data/lib/active_record/core.rb +273 -178
  111. data/lib/active_record/counter_cache.rb +69 -35
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +56 -27
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +46 -22
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +48 -13
  130. data/lib/active_record/encryption/encryptor.rb +35 -19
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +130 -28
  143. data/lib/active_record/errors.rb +154 -34
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +48 -10
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +236 -118
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +96 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +35 -10
  178. data/lib/active_record/railtie.rb +131 -87
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +147 -155
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +270 -108
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +97 -21
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +3 -2
  196. data/lib/active_record/relation/query_methods.rb +585 -109
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +15 -21
  200. data/lib/active_record/relation.rb +592 -92
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +90 -23
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +33 -11
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +23 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +108 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +3 -1
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/crud.rb +2 -0
  248. data/lib/arel/delete_manager.rb +5 -0
  249. data/lib/arel/errors.rb +10 -0
  250. data/lib/arel/factory_methods.rb +4 -0
  251. data/lib/arel/nodes/binary.rb +6 -7
  252. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  253. data/lib/arel/nodes/cte.rb +36 -0
  254. data/lib/arel/nodes/delete_statement.rb +4 -2
  255. data/lib/arel/nodes/fragments.rb +35 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  257. data/lib/arel/nodes/leading_join.rb +8 -0
  258. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  259. data/lib/arel/nodes/node.rb +115 -5
  260. data/lib/arel/nodes/sql_literal.rb +13 -0
  261. data/lib/arel/nodes/table_alias.rb +4 -0
  262. data/lib/arel/nodes/update_statement.rb +4 -2
  263. data/lib/arel/nodes.rb +6 -2
  264. data/lib/arel/predications.rb +3 -1
  265. data/lib/arel/select_manager.rb +7 -3
  266. data/lib/arel/table.rb +9 -5
  267. data/lib/arel/tree_manager.rb +8 -3
  268. data/lib/arel/update_manager.rb +7 -1
  269. data/lib/arel/visitors/dot.rb +3 -0
  270. data/lib/arel/visitors/mysql.rb +17 -5
  271. data/lib/arel/visitors/postgresql.rb +1 -12
  272. data/lib/arel/visitors/sqlite.rb +25 -0
  273. data/lib/arel/visitors/to_sql.rb +114 -34
  274. data/lib/arel/visitors/visitor.rb +2 -2
  275. data/lib/arel.rb +21 -3
  276. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  277. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  278. data/lib/rails/generators/active_record/migration.rb +3 -1
  279. data/lib/rails/generators/active_record/model/USAGE +113 -0
  280. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  281. metadata +56 -17
  282. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  283. data/lib/active_record/null_relation.rb +0 -63
@@ -10,35 +10,50 @@ require "active_record/connection_adapters/abstract/connection_pool/reaper"
10
10
  module ActiveRecord
11
11
  module ConnectionAdapters
12
12
  module AbstractPool # :nodoc:
13
- def get_schema_cache(connection)
14
- self.schema_cache ||= SchemaCache.new(connection)
15
- schema_cache.connection = connection
16
- schema_cache
17
- end
13
+ end
18
14
 
19
- def set_schema_cache(cache)
20
- self.schema_cache = cache
21
- end
15
+ class NullPool # :nodoc:
16
+ include ConnectionAdapters::AbstractPool
22
17
 
23
- def lazily_set_schema_cache
24
- return unless ActiveRecord.lazily_load_schema_cache
18
+ class NullConfig # :nodoc:
19
+ def method_missing(...)
20
+ nil
21
+ end
22
+ end
23
+ NULL_CONFIG = NullConfig.new # :nodoc:
25
24
 
26
- cache = SchemaCache.load_from(db_config.lazy_schema_cache_path)
27
- set_schema_cache(cache)
25
+ def initialize
26
+ super()
27
+ @mutex = Mutex.new
28
+ @server_version = nil
28
29
  end
29
- end
30
30
 
31
- class NullPool # :nodoc:
32
- include ConnectionAdapters::AbstractPool
31
+ def server_version(connection) # :nodoc:
32
+ @server_version || @mutex.synchronize { @server_version ||= connection.get_database_version }
33
+ end
33
34
 
34
- attr_accessor :schema_cache
35
+ def schema_reflection
36
+ SchemaReflection.new(nil)
37
+ end
35
38
 
39
+ def schema_cache; end
36
40
  def connection_class; end
41
+ def query_cache; end
37
42
  def checkin(_); end
38
43
  def remove(_); end
39
44
  def async_executor; end
45
+
46
+ def db_config
47
+ NULL_CONFIG
48
+ end
49
+
50
+ def dirties_query_cache
51
+ true
52
+ end
40
53
  end
41
54
 
55
+ # = Active Record Connection Pool
56
+ #
42
57
  # Connection pool base class for managing Active Record database
43
58
  # connections.
44
59
  #
@@ -53,19 +68,17 @@ module ActiveRecord
53
68
  # handle cases in which there are more threads than connections: if all
54
69
  # connections have been checked out, and a thread tries to checkout a
55
70
  # connection anyway, then ConnectionPool will wait until some other thread
56
- # has checked in a connection.
71
+ # has checked in a connection, or the +checkout_timeout+ has expired.
57
72
  #
58
73
  # == Obtaining (checking out) a connection
59
74
  #
60
75
  # Connections can be obtained and used from a connection pool in several
61
76
  # ways:
62
77
  #
63
- # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
64
- # as with Active Record 2.1 and
65
- # earlier (pre-connection-pooling). Eventually, when you're done with
66
- # the connection(s) and wish it to be returned to the pool, you call
67
- # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
68
- # 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
69
82
  # Action Pack's request handling cycle.
70
83
  # 2. Manually check out a connection from the pool with
71
84
  # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
@@ -78,6 +91,12 @@ module ActiveRecord
78
91
  # Connections in the pool are actually AbstractAdapter objects (or objects
79
92
  # compatible with AbstractAdapter's interface).
80
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
+ #
81
100
  # == Options
82
101
  #
83
102
  # There are several connection-pooling-related options that you can add to
@@ -100,16 +119,111 @@ module ActiveRecord
100
119
  # * private methods that require being called in a +synchronize+ blocks
101
120
  # are now explicitly documented
102
121
  class ConnectionPool
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 = {}
132
+ end
133
+
134
+ def clear
135
+ @map.clear
136
+ end
137
+
138
+ def [](key)
139
+ @map[key]
140
+ end
141
+
142
+ def []=(key, value)
143
+ @map.select! { |c, _| c&.alive? }
144
+ @map[key] = value
145
+ end
146
+ end
147
+ end
148
+
149
+ class Lease # :nodoc:
150
+ attr_accessor :connection, :sticky
151
+
152
+ def initialize
153
+ @connection = nil
154
+ @sticky = nil
155
+ end
156
+
157
+ def release
158
+ conn = @connection
159
+ @connection = nil
160
+ @sticky = nil
161
+ conn
162
+ end
163
+
164
+ def clear(connection)
165
+ if @connection == connection
166
+ @connection = nil
167
+ @sticky = nil
168
+ true
169
+ else
170
+ false
171
+ end
172
+ end
173
+ end
174
+
175
+ class LeaseRegistry # :nodoc:
176
+ def initialize
177
+ @mutex = Mutex.new
178
+ @map = WeakThreadKeyMap.new
179
+ end
180
+
181
+ def [](context)
182
+ @mutex.synchronize do
183
+ @map[context] ||= Lease.new
184
+ end
185
+ end
186
+
187
+ def clear
188
+ @mutex.synchronize do
189
+ @map.clear
190
+ end
191
+ end
192
+ end
193
+
194
+ module ExecutorHooks # :nodoc:
195
+ class << self
196
+ def run
197
+ # noop
198
+ end
199
+
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
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ class << self
214
+ def install_executor_hooks(executor = ActiveSupport::Executor)
215
+ executor.register_hook(ExecutorHooks)
216
+ end
217
+ end
218
+
103
219
  include MonitorMixin
104
- include QueryCache::ConnectionPoolConfiguration
220
+ prepend QueryCache::ConnectionPoolConfiguration
105
221
  include ConnectionAdapters::AbstractPool
106
222
 
107
223
  attr_accessor :automatic_reconnect, :checkout_timeout
108
- attr_reader :db_config, :size, :reaper, :pool_config, :connection_class, :async_executor, :role, :shard
224
+ attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
109
225
 
110
- alias_method :connection_klass, :connection_class
111
- deprecate :connection_klass
112
- delegate :schema_cache, :schema_cache=, to: :pool_config
226
+ delegate :schema_reflection, :server_version, to: :pool_config
113
227
 
114
228
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
229
  # object which describes database connection information (e.g. adapter,
@@ -122,7 +236,6 @@ module ActiveRecord
122
236
 
123
237
  @pool_config = pool_config
124
238
  @db_config = pool_config.db_config
125
- @connection_class = pool_config.connection_class
126
239
  @role = pool_config.role
127
240
  @shard = pool_config.shard
128
241
 
@@ -138,9 +251,9 @@ module ActiveRecord
138
251
  # then that +thread+ does indeed own that +conn+. However, an absence of such
139
252
  # mapping does not mean that the +thread+ doesn't own the said connection. In
140
253
  # that case +conn.owner+ attr should be consulted.
141
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
254
+ # Access and modification of <tt>@leases</tt> does not require
142
255
  # synchronization.
143
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
256
+ @leases = LeaseRegistry.new
144
257
 
145
258
  @connections = []
146
259
  @automatic_reconnect = true
@@ -153,73 +266,179 @@ module ActiveRecord
153
266
  @threads_blocking_new_connections = 0
154
267
 
155
268
  @available = ConnectionLeasingQueue.new self
156
-
157
- @lock_thread = false
269
+ @pinned_connection = nil
270
+ @pinned_connections_depth = 0
158
271
 
159
272
  @async_executor = build_async_executor
160
273
 
161
- lazily_set_schema_cache
274
+ @schema_cache = nil
162
275
 
163
276
  @reaper = Reaper.new(self, db_config.reaping_frequency)
164
277
  @reaper.run
165
278
  end
166
279
 
167
- def lock_thread=(lock_thread)
168
- if lock_thread
169
- @lock_thread = Thread.current
170
- else
171
- @lock_thread = nil
172
- 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)
173
310
  end
174
311
 
175
312
  # Retrieve the connection associated with the current thread, or call
176
313
  # #checkout to obtain one if necessary.
177
314
  #
178
- # #connection can be called any number of times; the connection is
315
+ # #lease_connection can be called any number of times; the connection is
179
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
+
180
328
  def connection
181
- @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
182
382
  end
183
383
 
184
384
  # Returns true if there is an open connection being used for the current thread.
185
385
  #
186
386
  # This method only works for connections that have been obtained through
187
- # #connection or #with_connection methods. Connections obtained through
387
+ # #lease_connection or #with_connection methods. Connections obtained through
188
388
  # #checkout will not be detected by #active_connection?
189
389
  def active_connection?
190
- @thread_cached_conns[connection_cache_key(current_thread)]
390
+ connection_lease.connection
191
391
  end
392
+ alias_method :active_connection, :active_connection? # :nodoc:
192
393
 
193
394
  # Signal that the thread is finished with the current connection.
194
395
  # #release_connection releases the connection-thread association
195
396
  # and returns the connection to the pool.
196
397
  #
197
398
  # This method only works for connections that have been obtained through
198
- # #connection or #with_connection methods, connections obtained through
399
+ # #lease_connection or #with_connection methods, connections obtained through
199
400
  # #checkout will not be automatically released.
200
- def release_connection(owner_thread = Thread.current)
201
- if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
401
+ def release_connection(existing_lease = nil)
402
+ if conn = connection_lease.release
202
403
  checkin conn
404
+ return true
203
405
  end
406
+ false
204
407
  end
205
408
 
206
- # If a connection obtained through #connection or #with_connection methods
207
- # already exists yield it to the block. If no such connection
208
- # exists checkout a connection, yield it to the block, and checkin the
209
- # connection when finished.
210
- def with_connection
211
- unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
212
- conn = connection
213
- fresh_connection = true
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
214
436
  end
215
- yield conn
216
- ensure
217
- release_connection if fresh_connection
218
437
  end
219
438
 
220
439
  # Returns true if a connection has already been opened.
221
440
  def connected?
222
- synchronize { @connections.any? }
441
+ synchronize { @connections.any?(&:connected?) }
223
442
  end
224
443
 
225
444
  # Returns an array containing the connections currently in the pool.
@@ -254,6 +473,7 @@ module ActiveRecord
254
473
  conn.disconnect!
255
474
  end
256
475
  @connections = []
476
+ @leases.clear
257
477
  @available.clear
258
478
  end
259
479
  end
@@ -280,7 +500,7 @@ module ActiveRecord
280
500
  @connections.each do |conn|
281
501
  conn.discard!
282
502
  end
283
- @connections = @available = @thread_cached_conns = nil
503
+ @connections = @available = @leases = nil
284
504
  end
285
505
  end
286
506
 
@@ -338,7 +558,26 @@ module ActiveRecord
338
558
  # Raises:
339
559
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
340
560
  def checkout(checkout_timeout = @checkout_timeout)
341
- 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
342
581
  end
343
582
 
344
583
  # Check-in a database connection back into the pool, indicating that you
@@ -347,9 +586,11 @@ module ActiveRecord
347
586
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
348
587
  # calling #checkout on this pool.
349
588
  def checkin(conn)
589
+ return if @pinned_connection.equal?(conn)
590
+
350
591
  conn.lock.synchronize do
351
592
  synchronize do
352
- remove_connection_from_thread_cache conn
593
+ connection_lease.clear(conn)
353
594
 
354
595
  conn._run_checkin_callbacks do
355
596
  conn.expire
@@ -448,8 +689,7 @@ module ActiveRecord
448
689
  @available.num_waiting
449
690
  end
450
691
 
451
- # Return connection pool's usage statistic
452
- # Example:
692
+ # Returns the connection pool's usage statistic.
453
693
  #
454
694
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
455
695
  def stat
@@ -471,7 +711,19 @@ module ActiveRecord
471
711
  Thread.pass
472
712
  end
473
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
+
474
722
  private
723
+ def connection_lease
724
+ @leases[ActiveSupport::IsolatedExecutionState.context]
725
+ end
726
+
475
727
  def build_async_executor
476
728
  case ActiveRecord.async_query_executor
477
729
  when :multi_thread_pool
@@ -500,19 +752,6 @@ module ActiveRecord
500
752
  end
501
753
  end
502
754
 
503
- #--
504
- # From the discussion on GitHub:
505
- # https://github.com/rails/rails/pull/14938#commitcomment-6601951
506
- # This hook-in method allows for easier monkey-patching fixes needed by
507
- # JRuby users that use Fibers.
508
- def connection_cache_key(thread)
509
- thread
510
- end
511
-
512
- def current_thread
513
- @lock_thread || Thread.current
514
- end
515
-
516
755
  # Take control of all existing connections so a "group" action such as
517
756
  # reload/disconnect can be performed safely. It is no longer enough to
518
757
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -526,17 +765,20 @@ module ActiveRecord
526
765
 
527
766
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
528
767
  collected_conns = synchronize do
768
+ reap # No need to wait for dead owners
769
+
529
770
  # account for our own connections
530
- @connections.select { |conn| conn.owner == Thread.current }
771
+ @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
531
772
  end
532
773
 
533
774
  newly_checked_out = []
534
775
  timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
535
776
 
536
- @available.with_a_bias_for(Thread.current) do
777
+ @available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
537
778
  loop do
538
779
  synchronize do
539
780
  return if collected_conns.size == @connections.size && @now_connecting == 0
781
+
540
782
  remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
541
783
  remaining_timeout = 0 if remaining_timeout < 0
542
784
  conn = checkout_for_exclusive_access(remaining_timeout)
@@ -580,14 +822,14 @@ module ActiveRecord
580
822
 
581
823
  thread_report = []
582
824
  @connections.each do |conn|
583
- unless conn.owner == Thread.current
825
+ unless conn.owner == ActiveSupport::IsolatedExecutionState.context
584
826
  thread_report << "#{conn} is owned by #{conn.owner}"
585
827
  end
586
828
  end
587
829
 
588
830
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
589
831
 
590
- raise ExclusiveConnectionTimeoutError, msg
832
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
591
833
  end
592
834
 
593
835
  def with_new_connections_blocked
@@ -641,22 +883,26 @@ module ActiveRecord
641
883
  conn
642
884
  else
643
885
  reap
644
- @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
645
893
  end
894
+ rescue ConnectionTimeoutError => ex
895
+ raise ex.set_pool(self)
646
896
  end
647
897
 
648
898
  #--
649
899
  # if owner_thread param is omitted, this must be called in synchronize block
650
900
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
651
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
652
- end
653
- alias_method :release, :remove_connection_from_thread_cache
654
-
655
- def new_connection
656
- Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
657
- conn.check_version
901
+ if owner_thread
902
+ @leases[owner_thread].clear(conn)
658
903
  end
659
904
  end
905
+ alias_method :release, :remove_connection_from_thread_cache
660
906
 
661
907
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
662
908
  # to the DB is done outside main synchronized section.
@@ -693,6 +939,12 @@ module ActiveRecord
693
939
  def adopt_connection(conn)
694
940
  conn.pool = self
695
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
696
948
  end
697
949
 
698
950
  def checkout_new_connection
@@ -702,10 +954,10 @@ module ActiveRecord
702
954
 
703
955
  def checkout_and_verify(c)
704
956
  c._run_checkout_callbacks do
705
- c.verify!
957
+ c.clean!
706
958
  end
707
959
  c
708
- rescue
960
+ rescue Exception
709
961
  remove c
710
962
  c.disconnect!
711
963
  raise
@@ -7,6 +7,11 @@ module ActiveRecord
7
7
  64
8
8
  end
9
9
 
10
+ # Returns the maximum length of a table name.
11
+ def table_name_length
12
+ max_identifier_length
13
+ end
14
+
10
15
  # Returns the maximum length of a table alias.
11
16
  def table_alias_length
12
17
  max_identifier_length