activerecord 7.0.8.7 → 7.2.2.1

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