activerecord 7.0.8 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +530 -2004
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  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 +25 -19
  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 +23 -8
  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 +26 -14
  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 +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +5 -5
  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 +328 -471
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  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 +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +58 -45
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +10 -24
  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 +317 -88
  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 +188 -63
  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 +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -128
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +274 -125
  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 +53 -54
  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 +101 -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 +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +368 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +364 -198
  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 +45 -46
  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 +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  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 +96 -104
  110. data/lib/active_record/core.rb +217 -174
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  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 +39 -10
  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 +44 -20
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +45 -10
  130. data/lib/active_record/encryption/encryptor.rb +17 -2
  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/message_pack_message_serializer.rb +76 -0
  135. data/lib/active_record/encryption/message_serializer.rb +6 -0
  136. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  137. data/lib/active_record/encryption/properties.rb +3 -3
  138. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  139. data/lib/active_record/encryption/scheme.rb +22 -21
  140. data/lib/active_record/encryption.rb +1 -0
  141. data/lib/active_record/enum.rb +122 -29
  142. data/lib/active_record/errors.rb +151 -31
  143. data/lib/active_record/explain.rb +21 -12
  144. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  145. data/lib/active_record/fixture_set/render_context.rb +2 -0
  146. data/lib/active_record/fixture_set/table_row.rb +29 -8
  147. data/lib/active_record/fixtures.rb +167 -97
  148. data/lib/active_record/future_result.rb +47 -8
  149. data/lib/active_record/gem_version.rb +3 -3
  150. data/lib/active_record/inheritance.rb +34 -18
  151. data/lib/active_record/insert_all.rb +72 -22
  152. data/lib/active_record/integration.rb +11 -8
  153. data/lib/active_record/internal_metadata.rb +124 -20
  154. data/lib/active_record/locking/optimistic.rb +8 -7
  155. data/lib/active_record/locking/pessimistic.rb +5 -2
  156. data/lib/active_record/log_subscriber.rb +18 -22
  157. data/lib/active_record/marshalling.rb +56 -0
  158. data/lib/active_record/message_pack.rb +124 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  160. data/lib/active_record/middleware/database_selector.rb +6 -8
  161. data/lib/active_record/middleware/shard_selector.rb +3 -1
  162. data/lib/active_record/migration/command_recorder.rb +106 -8
  163. data/lib/active_record/migration/compatibility.rb +147 -5
  164. data/lib/active_record/migration/default_strategy.rb +22 -0
  165. data/lib/active_record/migration/execution_strategy.rb +19 -0
  166. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  167. data/lib/active_record/migration.rb +234 -117
  168. data/lib/active_record/model_schema.rb +88 -103
  169. data/lib/active_record/nested_attributes.rb +35 -9
  170. data/lib/active_record/normalization.rb +163 -0
  171. data/lib/active_record/persistence.rb +168 -339
  172. data/lib/active_record/promise.rb +84 -0
  173. data/lib/active_record/query_cache.rb +19 -25
  174. data/lib/active_record/query_logs.rb +92 -52
  175. data/lib/active_record/query_logs_formatter.rb +41 -0
  176. data/lib/active_record/querying.rb +33 -8
  177. data/lib/active_record/railtie.rb +135 -86
  178. data/lib/active_record/railties/controller_runtime.rb +22 -7
  179. data/lib/active_record/railties/databases.rake +145 -154
  180. data/lib/active_record/railties/job_runtime.rb +23 -0
  181. data/lib/active_record/readonly_attributes.rb +32 -5
  182. data/lib/active_record/reflection.rb +259 -68
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  184. data/lib/active_record/relation/batches.rb +196 -61
  185. data/lib/active_record/relation/calculations.rb +249 -92
  186. data/lib/active_record/relation/delegation.rb +30 -19
  187. data/lib/active_record/relation/finder_methods.rb +93 -18
  188. data/lib/active_record/relation/merger.rb +6 -6
  189. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  190. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  191. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  192. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  193. data/lib/active_record/relation/predicate_builder.rb +28 -16
  194. data/lib/active_record/relation/query_attribute.rb +2 -1
  195. data/lib/active_record/relation/query_methods.rb +548 -94
  196. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  197. data/lib/active_record/relation/spawn_methods.rb +5 -4
  198. data/lib/active_record/relation/where_clause.rb +7 -19
  199. data/lib/active_record/relation.rb +580 -90
  200. data/lib/active_record/result.rb +49 -48
  201. data/lib/active_record/runtime_registry.rb +63 -1
  202. data/lib/active_record/sanitization.rb +70 -25
  203. data/lib/active_record/schema.rb +8 -7
  204. data/lib/active_record/schema_dumper.rb +63 -14
  205. data/lib/active_record/schema_migration.rb +75 -24
  206. data/lib/active_record/scoping/default.rb +15 -5
  207. data/lib/active_record/scoping/named.rb +2 -2
  208. data/lib/active_record/scoping.rb +2 -1
  209. data/lib/active_record/secure_password.rb +60 -0
  210. data/lib/active_record/secure_token.rb +21 -3
  211. data/lib/active_record/signed_id.rb +27 -6
  212. data/lib/active_record/statement_cache.rb +7 -7
  213. data/lib/active_record/store.rb +8 -8
  214. data/lib/active_record/suppressor.rb +3 -1
  215. data/lib/active_record/table_metadata.rb +1 -1
  216. data/lib/active_record/tasks/database_tasks.rb +180 -119
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  220. data/lib/active_record/test_fixtures.rb +170 -155
  221. data/lib/active_record/testing/query_assertions.rb +121 -0
  222. data/lib/active_record/timestamp.rb +31 -17
  223. data/lib/active_record/token_for.rb +123 -0
  224. data/lib/active_record/touch_later.rb +12 -7
  225. data/lib/active_record/transaction.rb +132 -0
  226. data/lib/active_record/transactions.rb +106 -24
  227. data/lib/active_record/translation.rb +0 -2
  228. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  229. data/lib/active_record/type/internal/timezone.rb +7 -2
  230. data/lib/active_record/type/serialized.rb +1 -3
  231. data/lib/active_record/type/time.rb +4 -0
  232. data/lib/active_record/type_caster/connection.rb +4 -4
  233. data/lib/active_record/validations/absence.rb +1 -1
  234. data/lib/active_record/validations/associated.rb +9 -3
  235. data/lib/active_record/validations/numericality.rb +5 -4
  236. data/lib/active_record/validations/presence.rb +5 -28
  237. data/lib/active_record/validations/uniqueness.rb +60 -11
  238. data/lib/active_record/validations.rb +12 -5
  239. data/lib/active_record/version.rb +1 -1
  240. data/lib/active_record.rb +247 -33
  241. data/lib/arel/alias_predication.rb +1 -1
  242. data/lib/arel/collectors/bind.rb +2 -0
  243. data/lib/arel/collectors/composite.rb +7 -0
  244. data/lib/arel/collectors/sql_string.rb +1 -1
  245. data/lib/arel/collectors/substitute_binds.rb +1 -1
  246. data/lib/arel/errors.rb +10 -0
  247. data/lib/arel/factory_methods.rb +4 -0
  248. data/lib/arel/nodes/binary.rb +6 -7
  249. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  250. data/lib/arel/nodes/cte.rb +36 -0
  251. data/lib/arel/nodes/fragments.rb +35 -0
  252. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  253. data/lib/arel/nodes/leading_join.rb +8 -0
  254. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  255. data/lib/arel/nodes/node.rb +115 -5
  256. data/lib/arel/nodes/sql_literal.rb +13 -0
  257. data/lib/arel/nodes/table_alias.rb +4 -0
  258. data/lib/arel/nodes.rb +6 -2
  259. data/lib/arel/predications.rb +3 -1
  260. data/lib/arel/select_manager.rb +1 -1
  261. data/lib/arel/table.rb +9 -5
  262. data/lib/arel/tree_manager.rb +8 -3
  263. data/lib/arel/update_manager.rb +2 -1
  264. data/lib/arel/visitors/dot.rb +1 -0
  265. data/lib/arel/visitors/mysql.rb +17 -5
  266. data/lib/arel/visitors/postgresql.rb +1 -12
  267. data/lib/arel/visitors/to_sql.rb +112 -34
  268. data/lib/arel/visitors/visitor.rb +2 -2
  269. data/lib/arel.rb +21 -3
  270. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  271. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  272. data/lib/rails/generators/active_record/migration.rb +3 -1
  273. data/lib/rails/generators/active_record/model/USAGE +113 -0
  274. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  275. metadata +56 -14
  276. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  277. data/lib/active_record/null_relation.rb +0 -63
@@ -10,35 +10,49 @@ 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
37
41
  def checkin(_); end
38
42
  def remove(_); end
39
43
  def async_executor; end
44
+
45
+ def db_config
46
+ NULL_CONFIG
47
+ end
48
+
49
+ def dirties_query_cache
50
+ true
51
+ end
40
52
  end
41
53
 
54
+ # = Active Record Connection Pool
55
+ #
42
56
  # Connection pool base class for managing Active Record database
43
57
  # connections.
44
58
  #
@@ -53,19 +67,17 @@ module ActiveRecord
53
67
  # handle cases in which there are more threads than connections: if all
54
68
  # connections have been checked out, and a thread tries to checkout a
55
69
  # connection anyway, then ConnectionPool will wait until some other thread
56
- # has checked in a connection.
70
+ # has checked in a connection, or the +checkout_timeout+ has expired.
57
71
  #
58
72
  # == Obtaining (checking out) a connection
59
73
  #
60
74
  # Connections can be obtained and used from a connection pool in several
61
75
  # ways:
62
76
  #
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
77
+ # 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection].
78
+ # When you're done with the connection(s) and wish it to be returned to the pool, you call
79
+ # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
80
+ # This is the default behavior for Active Record when used in conjunction with
69
81
  # Action Pack's request handling cycle.
70
82
  # 2. Manually check out a connection from the pool with
71
83
  # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
@@ -78,6 +90,12 @@ module ActiveRecord
78
90
  # Connections in the pool are actually AbstractAdapter objects (or objects
79
91
  # compatible with AbstractAdapter's interface).
80
92
  #
93
+ # While a thread has a connection checked out from the pool using one of the
94
+ # above three methods, that connection will automatically be the one used
95
+ # by ActiveRecord queries executing on that thread. It is not required to
96
+ # explicitly pass the checked out connection to \Rails models or queries, for
97
+ # example.
98
+ #
81
99
  # == Options
82
100
  #
83
101
  # There are several connection-pooling-related options that you can add to
@@ -100,16 +118,98 @@ module ActiveRecord
100
118
  # * private methods that require being called in a +synchronize+ blocks
101
119
  # are now explicitly documented
102
120
  class ConnectionPool
121
+ class Lease # :nodoc:
122
+ attr_accessor :connection, :sticky
123
+
124
+ def initialize
125
+ @connection = nil
126
+ @sticky = nil
127
+ end
128
+
129
+ def release
130
+ conn = @connection
131
+ @connection = nil
132
+ @sticky = nil
133
+ conn
134
+ end
135
+
136
+ def clear(connection)
137
+ if @connection == connection
138
+ @connection = nil
139
+ @sticky = nil
140
+ true
141
+ else
142
+ false
143
+ end
144
+ end
145
+ end
146
+
147
+ class LeaseRegistry # :nodoc:
148
+ if ObjectSpace.const_defined?(:WeakKeyMap) # RUBY_VERSION >= 3.3
149
+ WeakKeyMap = ::ObjectSpace::WeakKeyMap # :nodoc:
150
+ else
151
+ class WeakKeyMap # :nodoc:
152
+ def initialize
153
+ @map = ObjectSpace::WeakMap.new
154
+ @values = nil
155
+ @size = 0
156
+ end
157
+
158
+ alias_method :clear, :initialize
159
+
160
+ def [](key)
161
+ prune if @map.size != @size
162
+ @map[key]
163
+ end
164
+
165
+ def []=(key, value)
166
+ @map[key] = value
167
+ prune if @map.size != @size
168
+ value
169
+ end
170
+
171
+ def delete(key)
172
+ if value = self[key]
173
+ self[key] = nil
174
+ prune
175
+ end
176
+ value
177
+ end
178
+
179
+ private
180
+ def prune(force = false)
181
+ @values = @map.values
182
+ @size = @map.size
183
+ end
184
+ end
185
+ end
186
+
187
+ def initialize
188
+ @mutex = Mutex.new
189
+ @map = WeakKeyMap.new
190
+ end
191
+
192
+ def [](context)
193
+ @mutex.synchronize do
194
+ @map[context] ||= Lease.new
195
+ end
196
+ end
197
+
198
+ def clear
199
+ @mutex.synchronize do
200
+ @map = WeakKeyMap.new
201
+ end
202
+ end
203
+ end
204
+
103
205
  include MonitorMixin
104
- include QueryCache::ConnectionPoolConfiguration
206
+ prepend QueryCache::ConnectionPoolConfiguration
105
207
  include ConnectionAdapters::AbstractPool
106
208
 
107
209
  attr_accessor :automatic_reconnect, :checkout_timeout
108
- attr_reader :db_config, :size, :reaper, :pool_config, :connection_class, :async_executor, :role, :shard
210
+ attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
109
211
 
110
- alias_method :connection_klass, :connection_class
111
- deprecate :connection_klass
112
- delegate :schema_cache, :schema_cache=, to: :pool_config
212
+ delegate :schema_reflection, :server_version, to: :pool_config
113
213
 
114
214
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
215
  # object which describes database connection information (e.g. adapter,
@@ -122,7 +222,6 @@ module ActiveRecord
122
222
 
123
223
  @pool_config = pool_config
124
224
  @db_config = pool_config.db_config
125
- @connection_class = pool_config.connection_class
126
225
  @role = pool_config.role
127
226
  @shard = pool_config.shard
128
227
 
@@ -138,9 +237,9 @@ module ActiveRecord
138
237
  # then that +thread+ does indeed own that +conn+. However, an absence of such
139
238
  # mapping does not mean that the +thread+ doesn't own the said connection. In
140
239
  # that case +conn.owner+ attr should be consulted.
141
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
240
+ # Access and modification of <tt>@leases</tt> does not require
142
241
  # synchronization.
143
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
242
+ @leases = LeaseRegistry.new
144
243
 
145
244
  @connections = []
146
245
  @automatic_reconnect = true
@@ -153,73 +252,175 @@ module ActiveRecord
153
252
  @threads_blocking_new_connections = 0
154
253
 
155
254
  @available = ConnectionLeasingQueue.new self
156
-
157
- @lock_thread = false
255
+ @pinned_connection = nil
256
+ @pinned_connections_depth = 0
158
257
 
159
258
  @async_executor = build_async_executor
160
259
 
161
- lazily_set_schema_cache
260
+ @schema_cache = nil
162
261
 
163
262
  @reaper = Reaper.new(self, db_config.reaping_frequency)
164
263
  @reaper.run
165
264
  end
166
265
 
167
- def lock_thread=(lock_thread)
168
- if lock_thread
169
- @lock_thread = Thread.current
170
- else
171
- @lock_thread = nil
172
- end
266
+ def inspect # :nodoc:
267
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
268
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
269
+
270
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
271
+ end
272
+
273
+ def schema_cache
274
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
275
+ end
276
+
277
+ def schema_reflection=(schema_reflection)
278
+ pool_config.schema_reflection = schema_reflection
279
+ @schema_cache = nil
280
+ end
281
+
282
+ def migration_context # :nodoc:
283
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
284
+ end
285
+
286
+ def migrations_paths # :nodoc:
287
+ db_config.migrations_paths || Migrator.migrations_paths
288
+ end
289
+
290
+ def schema_migration # :nodoc:
291
+ SchemaMigration.new(self)
292
+ end
293
+
294
+ def internal_metadata # :nodoc:
295
+ InternalMetadata.new(self)
173
296
  end
174
297
 
175
298
  # Retrieve the connection associated with the current thread, or call
176
299
  # #checkout to obtain one if necessary.
177
300
  #
178
- # #connection can be called any number of times; the connection is
301
+ # #lease_connection can be called any number of times; the connection is
179
302
  # held in a cache keyed by a thread.
303
+ def lease_connection
304
+ lease = connection_lease
305
+ lease.sticky = true
306
+ lease.connection ||= checkout
307
+ end
308
+
309
+ def permanent_lease? # :nodoc:
310
+ connection_lease.sticky.nil?
311
+ end
312
+
180
313
  def connection
181
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
314
+ ActiveRecord.deprecator.warn(<<~MSG)
315
+ ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
316
+ and will be removed in Rails 8.0. Use #lease_connection instead.
317
+ MSG
318
+ lease_connection
319
+ end
320
+
321
+ def pin_connection!(lock_thread) # :nodoc:
322
+ @pinned_connection ||= (connection_lease&.connection || checkout)
323
+ @pinned_connections_depth += 1
324
+
325
+ # Any leased connection must be in @connections otherwise
326
+ # some methods like #connected? won't behave correctly
327
+ unless @connections.include?(@pinned_connection)
328
+ @connections << @pinned_connection
329
+ end
330
+
331
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
332
+ @pinned_connection.verify! # eagerly validate the connection
333
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
334
+ end
335
+
336
+ def unpin_connection! # :nodoc:
337
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
338
+
339
+ clean = true
340
+ @pinned_connection.lock.synchronize do
341
+ @pinned_connections_depth -= 1
342
+ connection = @pinned_connection
343
+ @pinned_connection = nil if @pinned_connections_depth.zero?
344
+
345
+ if connection.transaction_open?
346
+ connection.rollback_transaction
347
+ else
348
+ # Something committed or rolled back the transaction
349
+ clean = false
350
+ connection.reset!
351
+ end
352
+
353
+ if @pinned_connection.nil?
354
+ connection.lock_thread = nil
355
+ checkin(connection)
356
+ end
357
+ end
358
+
359
+ clean
360
+ end
361
+
362
+ def connection_class # :nodoc:
363
+ pool_config.connection_class
182
364
  end
183
365
 
184
366
  # Returns true if there is an open connection being used for the current thread.
185
367
  #
186
368
  # This method only works for connections that have been obtained through
187
- # #connection or #with_connection methods. Connections obtained through
369
+ # #lease_connection or #with_connection methods. Connections obtained through
188
370
  # #checkout will not be detected by #active_connection?
189
371
  def active_connection?
190
- @thread_cached_conns[connection_cache_key(current_thread)]
372
+ connection_lease.connection
191
373
  end
374
+ alias_method :active_connection, :active_connection? # :nodoc:
192
375
 
193
376
  # Signal that the thread is finished with the current connection.
194
377
  # #release_connection releases the connection-thread association
195
378
  # and returns the connection to the pool.
196
379
  #
197
380
  # This method only works for connections that have been obtained through
198
- # #connection or #with_connection methods, connections obtained through
381
+ # #lease_connection or #with_connection methods, connections obtained through
199
382
  # #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))
383
+ def release_connection(existing_lease = nil)
384
+ if conn = connection_lease.release
202
385
  checkin conn
386
+ return true
203
387
  end
388
+ false
204
389
  end
205
390
 
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
391
+ # Yields a connection from the connection pool to the block. If no connection
392
+ # is already checked out by the current thread, a connection will be checked
393
+ # out from the pool, yielded to the block, and then returned to the pool when
394
+ # the block is finished. If a connection has already been checked out on the
395
+ # current thread, such as via #lease_connection or #with_connection, that existing
396
+ # connection will be the one yielded and it will not be returned to the pool
397
+ # automatically at the end of the block; it is expected that such an existing
398
+ # connection will be properly returned to the pool by the code that checked
399
+ # it out.
400
+ def with_connection(prevent_permanent_checkout: false)
401
+ lease = connection_lease
402
+ sticky_was = lease.sticky
403
+ lease.sticky = false if prevent_permanent_checkout
404
+
405
+ if lease.connection
406
+ begin
407
+ yield lease.connection
408
+ ensure
409
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
410
+ end
411
+ else
412
+ begin
413
+ yield lease.connection = checkout
414
+ ensure
415
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
416
+ release_connection(lease) unless lease.sticky
417
+ end
214
418
  end
215
- yield conn
216
- ensure
217
- release_connection if fresh_connection
218
419
  end
219
420
 
220
421
  # Returns true if a connection has already been opened.
221
422
  def connected?
222
- synchronize { @connections.any? }
423
+ synchronize { @connections.any?(&:connected?) }
223
424
  end
224
425
 
225
426
  # Returns an array containing the connections currently in the pool.
@@ -254,6 +455,7 @@ module ActiveRecord
254
455
  conn.disconnect!
255
456
  end
256
457
  @connections = []
458
+ @leases.clear
257
459
  @available.clear
258
460
  end
259
461
  end
@@ -280,7 +482,7 @@ module ActiveRecord
280
482
  @connections.each do |conn|
281
483
  conn.discard!
282
484
  end
283
- @connections = @available = @thread_cached_conns = nil
485
+ @connections = @available = @leases = nil
284
486
  end
285
487
  end
286
488
 
@@ -338,7 +540,21 @@ module ActiveRecord
338
540
  # Raises:
339
541
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
340
542
  def checkout(checkout_timeout = @checkout_timeout)
341
- checkout_and_verify(acquire_connection(checkout_timeout))
543
+ if @pinned_connection
544
+ @pinned_connection.lock.synchronize do
545
+ synchronize do
546
+ @pinned_connection.verify!
547
+ # Any leased connection must be in @connections otherwise
548
+ # some methods like #connected? won't behave correctly
549
+ unless @connections.include?(@pinned_connection)
550
+ @connections << @pinned_connection
551
+ end
552
+ end
553
+ end
554
+ @pinned_connection
555
+ else
556
+ checkout_and_verify(acquire_connection(checkout_timeout))
557
+ end
342
558
  end
343
559
 
344
560
  # Check-in a database connection back into the pool, indicating that you
@@ -347,9 +563,11 @@ module ActiveRecord
347
563
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
348
564
  # calling #checkout on this pool.
349
565
  def checkin(conn)
566
+ return if @pinned_connection.equal?(conn)
567
+
350
568
  conn.lock.synchronize do
351
569
  synchronize do
352
- remove_connection_from_thread_cache conn
570
+ connection_lease.clear(conn)
353
571
 
354
572
  conn._run_checkin_callbacks do
355
573
  conn.expire
@@ -412,6 +630,8 @@ module ActiveRecord
412
630
  remove conn
413
631
  end
414
632
  end
633
+
634
+ prune_thread_cache
415
635
  end
416
636
 
417
637
  # Disconnect all connections that have been idle for at least
@@ -448,8 +668,7 @@ module ActiveRecord
448
668
  @available.num_waiting
449
669
  end
450
670
 
451
- # Return connection pool's usage statistic
452
- # Example:
671
+ # Returns the connection pool's usage statistic.
453
672
  #
454
673
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
455
674
  def stat
@@ -472,6 +691,10 @@ module ActiveRecord
472
691
  end
473
692
 
474
693
  private
694
+ def connection_lease
695
+ @leases[ActiveSupport::IsolatedExecutionState.context]
696
+ end
697
+
475
698
  def build_async_executor
476
699
  case ActiveRecord.async_query_executor
477
700
  when :multi_thread_pool
@@ -500,19 +723,6 @@ module ActiveRecord
500
723
  end
501
724
  end
502
725
 
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
726
  # Take control of all existing connections so a "group" action such as
517
727
  # reload/disconnect can be performed safely. It is no longer enough to
518
728
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -526,17 +736,20 @@ module ActiveRecord
526
736
 
527
737
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
528
738
  collected_conns = synchronize do
739
+ reap # No need to wait for dead owners
740
+
529
741
  # account for our own connections
530
- @connections.select { |conn| conn.owner == Thread.current }
742
+ @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
531
743
  end
532
744
 
533
745
  newly_checked_out = []
534
746
  timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
535
747
 
536
- @available.with_a_bias_for(Thread.current) do
748
+ @available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
537
749
  loop do
538
750
  synchronize do
539
751
  return if collected_conns.size == @connections.size && @now_connecting == 0
752
+
540
753
  remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
541
754
  remaining_timeout = 0 if remaining_timeout < 0
542
755
  conn = checkout_for_exclusive_access(remaining_timeout)
@@ -580,14 +793,14 @@ module ActiveRecord
580
793
 
581
794
  thread_report = []
582
795
  @connections.each do |conn|
583
- unless conn.owner == Thread.current
796
+ unless conn.owner == ActiveSupport::IsolatedExecutionState.context
584
797
  thread_report << "#{conn} is owned by #{conn.owner}"
585
798
  end
586
799
  end
587
800
 
588
801
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
589
802
 
590
- raise ExclusiveConnectionTimeoutError, msg
803
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
591
804
  end
592
805
 
593
806
  def with_new_connections_blocked
@@ -641,21 +854,31 @@ module ActiveRecord
641
854
  conn
642
855
  else
643
856
  reap
644
- @available.poll(checkout_timeout)
857
+ # Retry after reaping, which may return an available connection,
858
+ # remove an inactive connection, or both
859
+ if conn = @available.poll || try_to_checkout_new_connection
860
+ conn
861
+ else
862
+ @available.poll(checkout_timeout)
863
+ end
645
864
  end
865
+ rescue ConnectionTimeoutError => ex
866
+ raise ex.set_pool(self)
646
867
  end
647
868
 
648
869
  #--
649
870
  # if owner_thread param is omitted, this must be called in synchronize block
650
871
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
651
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
872
+ @leases[owner_thread].clear(conn)
652
873
  end
653
874
  alias_method :release, :remove_connection_from_thread_cache
654
875
 
655
876
  def new_connection
656
- Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
657
- conn.check_version
658
- end
877
+ connection = db_config.new_connection
878
+ connection.pool = self
879
+ connection
880
+ rescue ConnectionNotEstablished => ex
881
+ raise ex.set_pool(self)
659
882
  end
660
883
 
661
884
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
@@ -693,6 +916,12 @@ module ActiveRecord
693
916
  def adopt_connection(conn)
694
917
  conn.pool = self
695
918
  @connections << conn
919
+
920
+ # We just created the first connection, it's time to load the schema
921
+ # cache if that wasn't eagerly done before
922
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
923
+ schema_cache.load!
924
+ end
696
925
  end
697
926
 
698
927
  def checkout_new_connection
@@ -702,10 +931,10 @@ module ActiveRecord
702
931
 
703
932
  def checkout_and_verify(c)
704
933
  c._run_checkout_callbacks do
705
- c.verify!
934
+ c.clean!
706
935
  end
707
936
  c
708
- rescue
937
+ rescue Exception
709
938
  remove c
710
939
  c.disconnect!
711
940
  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