activerecord 7.0.0 → 7.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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +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 +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  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 +28 -18
  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 +18 -14
  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 +2 -4
  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 +378 -491
  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 +153 -70
  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 +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  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 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  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 +297 -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 +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  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 +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. 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,80 @@ 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
+
103
187
  include MonitorMixin
104
- include QueryCache::ConnectionPoolConfiguration
188
+ prepend QueryCache::ConnectionPoolConfiguration
105
189
  include ConnectionAdapters::AbstractPool
106
190
 
107
191
  attr_accessor :automatic_reconnect, :checkout_timeout
108
- attr_reader :db_config, :size, :reaper, :pool_config, :connection_class, :async_executor, :role, :shard
192
+ attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
109
193
 
110
- alias_method :connection_klass, :connection_class
111
- deprecate :connection_klass
112
- delegate :schema_cache, :schema_cache=, to: :pool_config
194
+ delegate :schema_reflection, :server_version, to: :pool_config
113
195
 
114
196
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
197
  # object which describes database connection information (e.g. adapter,
@@ -122,7 +204,6 @@ module ActiveRecord
122
204
 
123
205
  @pool_config = pool_config
124
206
  @db_config = pool_config.db_config
125
- @connection_class = pool_config.connection_class
126
207
  @role = pool_config.role
127
208
  @shard = pool_config.shard
128
209
 
@@ -138,9 +219,9 @@ module ActiveRecord
138
219
  # then that +thread+ does indeed own that +conn+. However, an absence of such
139
220
  # mapping does not mean that the +thread+ doesn't own the said connection. In
140
221
  # that case +conn.owner+ attr should be consulted.
141
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
222
+ # Access and modification of <tt>@leases</tt> does not require
142
223
  # synchronization.
143
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
224
+ @leases = LeaseRegistry.new
144
225
 
145
226
  @connections = []
146
227
  @automatic_reconnect = true
@@ -153,73 +234,175 @@ module ActiveRecord
153
234
  @threads_blocking_new_connections = 0
154
235
 
155
236
  @available = ConnectionLeasingQueue.new self
156
-
157
- @lock_thread = false
237
+ @pinned_connection = nil
238
+ @pinned_connections_depth = 0
158
239
 
159
240
  @async_executor = build_async_executor
160
241
 
161
- lazily_set_schema_cache
242
+ @schema_cache = nil
162
243
 
163
244
  @reaper = Reaper.new(self, db_config.reaping_frequency)
164
245
  @reaper.run
165
246
  end
166
247
 
167
- def lock_thread=(lock_thread)
168
- if lock_thread
169
- @lock_thread = Thread.current
170
- else
171
- @lock_thread = nil
172
- end
248
+ def inspect # :nodoc:
249
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
250
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
251
+
252
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
253
+ end
254
+
255
+ def schema_cache
256
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
257
+ end
258
+
259
+ def schema_reflection=(schema_reflection)
260
+ pool_config.schema_reflection = schema_reflection
261
+ @schema_cache = nil
262
+ end
263
+
264
+ def migration_context # :nodoc:
265
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
266
+ end
267
+
268
+ def migrations_paths # :nodoc:
269
+ db_config.migrations_paths || Migrator.migrations_paths
270
+ end
271
+
272
+ def schema_migration # :nodoc:
273
+ SchemaMigration.new(self)
274
+ end
275
+
276
+ def internal_metadata # :nodoc:
277
+ InternalMetadata.new(self)
173
278
  end
174
279
 
175
280
  # Retrieve the connection associated with the current thread, or call
176
281
  # #checkout to obtain one if necessary.
177
282
  #
178
- # #connection can be called any number of times; the connection is
283
+ # #lease_connection can be called any number of times; the connection is
179
284
  # held in a cache keyed by a thread.
285
+ def lease_connection
286
+ lease = connection_lease
287
+ lease.sticky = true
288
+ lease.connection ||= checkout
289
+ end
290
+
291
+ def permanent_lease? # :nodoc:
292
+ connection_lease.sticky.nil?
293
+ end
294
+
180
295
  def connection
181
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
296
+ ActiveRecord.deprecator.warn(<<~MSG)
297
+ ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
298
+ and will be removed in Rails 8.0. Use #lease_connection instead.
299
+ MSG
300
+ lease_connection
301
+ end
302
+
303
+ def pin_connection!(lock_thread) # :nodoc:
304
+ @pinned_connection ||= (connection_lease&.connection || checkout)
305
+ @pinned_connections_depth += 1
306
+
307
+ # Any leased connection must be in @connections otherwise
308
+ # some methods like #connected? won't behave correctly
309
+ unless @connections.include?(@pinned_connection)
310
+ @connections << @pinned_connection
311
+ end
312
+
313
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
314
+ @pinned_connection.verify! # eagerly validate the connection
315
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
316
+ end
317
+
318
+ def unpin_connection! # :nodoc:
319
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
320
+
321
+ clean = true
322
+ @pinned_connection.lock.synchronize do
323
+ @pinned_connections_depth -= 1
324
+ connection = @pinned_connection
325
+ @pinned_connection = nil if @pinned_connections_depth.zero?
326
+
327
+ if connection.transaction_open?
328
+ connection.rollback_transaction
329
+ else
330
+ # Something committed or rolled back the transaction
331
+ clean = false
332
+ connection.reset!
333
+ end
334
+
335
+ if @pinned_connection.nil?
336
+ connection.lock_thread = nil
337
+ checkin(connection)
338
+ end
339
+ end
340
+
341
+ clean
342
+ end
343
+
344
+ def connection_class # :nodoc:
345
+ pool_config.connection_class
182
346
  end
183
347
 
184
348
  # Returns true if there is an open connection being used for the current thread.
185
349
  #
186
350
  # This method only works for connections that have been obtained through
187
- # #connection or #with_connection methods. Connections obtained through
351
+ # #lease_connection or #with_connection methods. Connections obtained through
188
352
  # #checkout will not be detected by #active_connection?
189
353
  def active_connection?
190
- @thread_cached_conns[connection_cache_key(current_thread)]
354
+ connection_lease.connection
191
355
  end
356
+ alias_method :active_connection, :active_connection? # :nodoc:
192
357
 
193
358
  # Signal that the thread is finished with the current connection.
194
359
  # #release_connection releases the connection-thread association
195
360
  # and returns the connection to the pool.
196
361
  #
197
362
  # This method only works for connections that have been obtained through
198
- # #connection or #with_connection methods, connections obtained through
363
+ # #lease_connection or #with_connection methods, connections obtained through
199
364
  # #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))
365
+ def release_connection(existing_lease = nil)
366
+ if conn = connection_lease.release
202
367
  checkin conn
368
+ return true
203
369
  end
370
+ false
204
371
  end
205
372
 
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
373
+ # Yields a connection from the connection pool to the block. If no connection
374
+ # is already checked out by the current thread, a connection will be checked
375
+ # out from the pool, yielded to the block, and then returned to the pool when
376
+ # the block is finished. If a connection has already been checked out on the
377
+ # current thread, such as via #lease_connection or #with_connection, that existing
378
+ # connection will be the one yielded and it will not be returned to the pool
379
+ # automatically at the end of the block; it is expected that such an existing
380
+ # connection will be properly returned to the pool by the code that checked
381
+ # it out.
382
+ def with_connection(prevent_permanent_checkout: false)
383
+ lease = connection_lease
384
+ sticky_was = lease.sticky
385
+ lease.sticky = false if prevent_permanent_checkout
386
+
387
+ if lease.connection
388
+ begin
389
+ yield lease.connection
390
+ ensure
391
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
392
+ end
393
+ else
394
+ begin
395
+ yield lease.connection = checkout
396
+ ensure
397
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
398
+ release_connection(lease) unless lease.sticky
399
+ end
214
400
  end
215
- yield conn
216
- ensure
217
- release_connection if fresh_connection
218
401
  end
219
402
 
220
403
  # Returns true if a connection has already been opened.
221
404
  def connected?
222
- synchronize { @connections.any? }
405
+ synchronize { @connections.any?(&:connected?) }
223
406
  end
224
407
 
225
408
  # Returns an array containing the connections currently in the pool.
@@ -254,6 +437,7 @@ module ActiveRecord
254
437
  conn.disconnect!
255
438
  end
256
439
  @connections = []
440
+ @leases.clear
257
441
  @available.clear
258
442
  end
259
443
  end
@@ -280,7 +464,7 @@ module ActiveRecord
280
464
  @connections.each do |conn|
281
465
  conn.discard!
282
466
  end
283
- @connections = @available = @thread_cached_conns = nil
467
+ @connections = @available = @leases = nil
284
468
  end
285
469
  end
286
470
 
@@ -338,7 +522,21 @@ module ActiveRecord
338
522
  # Raises:
339
523
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
340
524
  def checkout(checkout_timeout = @checkout_timeout)
341
- checkout_and_verify(acquire_connection(checkout_timeout))
525
+ if @pinned_connection
526
+ @pinned_connection.lock.synchronize do
527
+ synchronize do
528
+ @pinned_connection.verify!
529
+ # Any leased connection must be in @connections otherwise
530
+ # some methods like #connected? won't behave correctly
531
+ unless @connections.include?(@pinned_connection)
532
+ @connections << @pinned_connection
533
+ end
534
+ end
535
+ end
536
+ @pinned_connection
537
+ else
538
+ checkout_and_verify(acquire_connection(checkout_timeout))
539
+ end
342
540
  end
343
541
 
344
542
  # Check-in a database connection back into the pool, indicating that you
@@ -347,9 +545,11 @@ module ActiveRecord
347
545
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
348
546
  # calling #checkout on this pool.
349
547
  def checkin(conn)
548
+ return if @pinned_connection.equal?(conn)
549
+
350
550
  conn.lock.synchronize do
351
551
  synchronize do
352
- remove_connection_from_thread_cache conn
552
+ connection_lease.clear(conn)
353
553
 
354
554
  conn._run_checkin_callbacks do
355
555
  conn.expire
@@ -448,8 +648,7 @@ module ActiveRecord
448
648
  @available.num_waiting
449
649
  end
450
650
 
451
- # Return connection pool's usage statistic
452
- # Example:
651
+ # Returns the connection pool's usage statistic.
453
652
  #
454
653
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
455
654
  def stat
@@ -472,6 +671,10 @@ module ActiveRecord
472
671
  end
473
672
 
474
673
  private
674
+ def connection_lease
675
+ @leases[ActiveSupport::IsolatedExecutionState.context]
676
+ end
677
+
475
678
  def build_async_executor
476
679
  case ActiveRecord.async_query_executor
477
680
  when :multi_thread_pool
@@ -500,19 +703,6 @@ module ActiveRecord
500
703
  end
501
704
  end
502
705
 
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
706
  # Take control of all existing connections so a "group" action such as
517
707
  # reload/disconnect can be performed safely. It is no longer enough to
518
708
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -526,17 +716,20 @@ module ActiveRecord
526
716
 
527
717
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
528
718
  collected_conns = synchronize do
719
+ reap # No need to wait for dead owners
720
+
529
721
  # account for our own connections
530
- @connections.select { |conn| conn.owner == Thread.current }
722
+ @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
531
723
  end
532
724
 
533
725
  newly_checked_out = []
534
726
  timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
535
727
 
536
- @available.with_a_bias_for(Thread.current) do
728
+ @available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
537
729
  loop do
538
730
  synchronize do
539
731
  return if collected_conns.size == @connections.size && @now_connecting == 0
732
+
540
733
  remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
541
734
  remaining_timeout = 0 if remaining_timeout < 0
542
735
  conn = checkout_for_exclusive_access(remaining_timeout)
@@ -580,14 +773,14 @@ module ActiveRecord
580
773
 
581
774
  thread_report = []
582
775
  @connections.each do |conn|
583
- unless conn.owner == Thread.current
776
+ unless conn.owner == ActiveSupport::IsolatedExecutionState.context
584
777
  thread_report << "#{conn} is owned by #{conn.owner}"
585
778
  end
586
779
  end
587
780
 
588
781
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
589
782
 
590
- raise ExclusiveConnectionTimeoutError, msg
783
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
591
784
  end
592
785
 
593
786
  def with_new_connections_blocked
@@ -641,21 +834,31 @@ module ActiveRecord
641
834
  conn
642
835
  else
643
836
  reap
644
- @available.poll(checkout_timeout)
837
+ # Retry after reaping, which may return an available connection,
838
+ # remove an inactive connection, or both
839
+ if conn = @available.poll || try_to_checkout_new_connection
840
+ conn
841
+ else
842
+ @available.poll(checkout_timeout)
843
+ end
645
844
  end
845
+ rescue ConnectionTimeoutError => ex
846
+ raise ex.set_pool(self)
646
847
  end
647
848
 
648
849
  #--
649
850
  # if owner_thread param is omitted, this must be called in synchronize block
650
851
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
651
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
852
+ @leases[owner_thread].clear(conn)
652
853
  end
653
854
  alias_method :release, :remove_connection_from_thread_cache
654
855
 
655
856
  def new_connection
656
- Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
657
- conn.check_version
658
- end
857
+ connection = db_config.new_connection
858
+ connection.pool = self
859
+ connection
860
+ rescue ConnectionNotEstablished => ex
861
+ raise ex.set_pool(self)
659
862
  end
660
863
 
661
864
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
@@ -693,6 +896,12 @@ module ActiveRecord
693
896
  def adopt_connection(conn)
694
897
  conn.pool = self
695
898
  @connections << conn
899
+
900
+ # We just created the first connection, it's time to load the schema
901
+ # cache if that wasn't eagerly done before
902
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
903
+ schema_cache.load!
904
+ end
696
905
  end
697
906
 
698
907
  def checkout_new_connection
@@ -702,10 +911,10 @@ module ActiveRecord
702
911
 
703
912
  def checkout_and_verify(c)
704
913
  c._run_checkout_callbacks do
705
- c.verify!
914
+ c.clean!
706
915
  end
707
916
  c
708
- rescue
917
+ rescue Exception
709
918
  remove c
710
919
  c.disconnect!
711
920
  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