activerecord 7.0.0 → 7.1.2

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1701 -1039
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +362 -236
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. data/lib/active_record/null_relation.rb +0 -63
@@ -5,6 +5,8 @@ require "concurrent/map"
5
5
 
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters
8
+ # = Active Record Connection Handler
9
+ #
8
10
  # ConnectionHandler is a collection of ConnectionPool objects. It is used
9
11
  # for keeping separate connection pools that connect to different databases.
10
12
  #
@@ -40,7 +42,7 @@ module ActiveRecord
40
42
  # but the Book model connects to a separate database called "library_db"
41
43
  # (this can even be a database on a different machine).
42
44
  #
43
- # Book, ScaryBook and GoodBook will all use the same connection pool to
45
+ # Book, ScaryBook, and GoodBook will all use the same connection pool to
44
46
  # "library_db" while Author, BankAccount, and any other models you create
45
47
  # will use the default connection pool to "my_application".
46
48
  #
@@ -56,7 +58,7 @@ module ActiveRecord
56
58
  FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
57
59
  private_constant :FINALIZER
58
60
 
59
- class StringConnectionOwner # :nodoc:
61
+ class StringConnectionName # :nodoc:
60
62
  attr_reader :name
61
63
 
62
64
  def initialize(name)
@@ -73,8 +75,8 @@ module ActiveRecord
73
75
  end
74
76
 
75
77
  def initialize
76
- # These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name).
77
- @owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
78
+ # These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
79
+ @connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
78
80
 
79
81
  # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
80
82
  ObjectSpace.define_finalizer self, FINALIZER
@@ -88,121 +90,154 @@ module ActiveRecord
88
90
  ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes
89
91
  end
90
92
 
91
- # Prevent writing to the database regardless of role.
92
- #
93
- # In some cases you may want to prevent writes to the database
94
- # even if you are on a database that can write. +while_preventing_writes+
95
- # will prevent writes to the database for the duration of the block.
96
- #
97
- # This method does not provide the same protection as a readonly
98
- # user and is meant to be a safeguard against accidental writes.
99
- #
100
- # See +READ_QUERY+ for the queries that are blocked by this
101
- # method.
102
- def while_preventing_writes(enabled = true)
103
- unless ActiveRecord.legacy_connection_handling
104
- raise NotImplementedError, "`while_preventing_writes` is only available on the connection_handler with legacy_connection_handling"
105
- end
106
-
107
- original, self.prevent_writes = self.prevent_writes, enabled
108
- yield
109
- ensure
110
- self.prevent_writes = original
111
- end
112
-
113
93
  def connection_pool_names # :nodoc:
114
- owner_to_pool_manager.keys
94
+ connection_name_to_pool_manager.keys
115
95
  end
116
96
 
117
97
  def all_connection_pools
118
- owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
98
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
99
+ The `all_connection_pools` method is deprecated in favor of `connection_pool_list`.
100
+ Call `connection_pool_list(:all)` to get the same behavior as `all_connection_pools`.
101
+ MSG
102
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
119
103
  end
120
104
 
121
- def connection_pool_list(role = ActiveRecord::Base.current_role)
122
- owner_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
105
+ # Returns the pools for a connection handler and given role. If +:all+ is passed,
106
+ # all pools belonging to the connection handler will be returned.
107
+ def connection_pool_list(role = nil)
108
+ if role.nil?
109
+ deprecation_for_pool_handling(__method__)
110
+ role = ActiveRecord::Base.current_role
111
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
112
+ elsif role == :all
113
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
114
+ else
115
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
116
+ end
123
117
  end
124
118
  alias :connection_pools :connection_pool_list
125
119
 
126
- def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
127
- owner_name = StringConnectionOwner.new(config.to_s) if config.is_a?(Symbol)
120
+ def each_connection_pool(role = nil, &block) # :nodoc:
121
+ role = nil if role == :all
122
+ return enum_for(__method__, role) unless block_given?
123
+
124
+ connection_name_to_pool_manager.each_value do |manager|
125
+ manager.each_pool_config(role) do |pool_config|
126
+ yield pool_config.pool
127
+ end
128
+ end
129
+ end
130
+
131
+ def establish_connection(config, owner_name: Base, role: Base.current_role, shard: Base.current_shard, clobber: false)
132
+ owner_name = determine_owner_name(owner_name, config)
128
133
 
129
134
  pool_config = resolve_pool_config(config, owner_name, role, shard)
130
135
  db_config = pool_config.db_config
131
136
 
132
- # Protects the connection named `ActiveRecord::Base` from being removed
133
- # if the user calls `establish_connection :primary`.
134
- if owner_to_pool_manager.key?(pool_config.connection_specification_name)
135
- remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard)
136
- end
137
+ pool_manager = set_pool_manager(pool_config.connection_name)
137
138
 
138
- message_bus = ActiveSupport::Notifications.instrumenter
139
- payload = {}
140
- if pool_config
141
- payload[:spec_name] = pool_config.connection_specification_name
142
- payload[:shard] = shard
143
- payload[:config] = db_config.configuration_hash
144
- end
139
+ # If there is an existing pool with the same values as the pool_config
140
+ # don't remove the connection. Connections should only be removed if we are
141
+ # establishing a connection on a class that is already connected to a different
142
+ # configuration.
143
+ existing_pool_config = pool_manager.get_pool_config(role, shard)
145
144
 
146
- if ActiveRecord.legacy_connection_handling
147
- owner_to_pool_manager[pool_config.connection_specification_name] ||= LegacyPoolManager.new
148
- else
149
- owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
150
- end
151
- pool_manager = get_pool_manager(pool_config.connection_specification_name)
152
- pool_manager.set_pool_config(role, shard, pool_config)
145
+ if !clobber && existing_pool_config && existing_pool_config.db_config == db_config
146
+ # Update the pool_config's connection class if it differs. This is used
147
+ # for ensuring that ActiveRecord::Base and the primary_abstract_class use
148
+ # the same pool. Without this granular swapping will not work correctly.
149
+ if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
150
+ existing_pool_config.connection_class = owner_name
151
+ end
153
152
 
154
- message_bus.instrument("!connection.active_record", payload) do
155
- pool_config.pool
153
+ existing_pool_config.pool
154
+ else
155
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
156
+ pool_manager.set_pool_config(role, shard, pool_config)
157
+
158
+ payload = {
159
+ connection_name: pool_config.connection_name,
160
+ role: role,
161
+ shard: shard,
162
+ config: db_config.configuration_hash
163
+ }
164
+
165
+ ActiveSupport::Notifications.instrumenter.instrument("!connection.active_record", payload) do
166
+ pool_config.pool
167
+ end
156
168
  end
157
169
  end
158
170
 
159
171
  # Returns true if there are any active connections among the connection
160
172
  # pools that the ConnectionHandler is managing.
161
- def active_connections?(role = ActiveRecord::Base.current_role)
162
- connection_pool_list(role).any?(&:active_connection?)
173
+ def active_connections?(role = nil)
174
+ if role.nil?
175
+ deprecation_for_pool_handling(__method__)
176
+ role = ActiveRecord::Base.current_role
177
+ end
178
+
179
+ each_connection_pool(role).any?(&:active_connection?)
163
180
  end
164
181
 
165
182
  # Returns any connections in use by the current thread back to the pool,
166
183
  # and also returns connections to the pool cached by threads that are no
167
184
  # longer alive.
168
- def clear_active_connections!(role = ActiveRecord::Base.current_role)
169
- connection_pool_list(role).each(&:release_connection)
185
+ def clear_active_connections!(role = nil)
186
+ if role.nil?
187
+ deprecation_for_pool_handling(__method__)
188
+ role = ActiveRecord::Base.current_role
189
+ end
190
+
191
+ each_connection_pool(role).each(&:release_connection)
170
192
  end
171
193
 
172
194
  # Clears the cache which maps classes.
173
195
  #
174
196
  # See ConnectionPool#clear_reloadable_connections! for details.
175
- def clear_reloadable_connections!(role = ActiveRecord::Base.current_role)
176
- connection_pool_list(role).each(&:clear_reloadable_connections!)
197
+ def clear_reloadable_connections!(role = nil)
198
+ if role.nil?
199
+ deprecation_for_pool_handling(__method__)
200
+ role = ActiveRecord::Base.current_role
201
+ end
202
+
203
+ each_connection_pool(role).each(&:clear_reloadable_connections!)
177
204
  end
178
205
 
179
- def clear_all_connections!(role = ActiveRecord::Base.current_role)
180
- connection_pool_list(role).each(&:disconnect!)
206
+ def clear_all_connections!(role = nil)
207
+ if role.nil?
208
+ deprecation_for_pool_handling(__method__)
209
+ role = ActiveRecord::Base.current_role
210
+ end
211
+
212
+ each_connection_pool(role).each(&:disconnect!)
181
213
  end
182
214
 
183
215
  # Disconnects all currently idle connections.
184
216
  #
185
217
  # See ConnectionPool#flush! for details.
186
- def flush_idle_connections!(role = ActiveRecord::Base.current_role)
187
- connection_pool_list(role).each(&:flush!)
218
+ def flush_idle_connections!(role = nil)
219
+ if role.nil?
220
+ deprecation_for_pool_handling(__method__)
221
+ role = ActiveRecord::Base.current_role
222
+ end
223
+
224
+ each_connection_pool(role).each(&:flush!)
188
225
  end
189
226
 
190
227
  # Locate the connection of the nearest super class. This can be an
191
228
  # active or defined connection: if it is the latter, it will be
192
229
  # opened and set as the active connection for the class it was defined
193
230
  # for (not necessarily the current class).
194
- def retrieve_connection(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
195
- pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
231
+ def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
232
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
196
233
 
197
234
  unless pool
198
235
  if shard != ActiveRecord::Base.default_shard
199
- message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard."
200
- elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
201
- message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
236
+ message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
202
237
  elsif role != ActiveRecord::Base.default_role
203
- message = "No connection pool for '#{spec_name}' found for the '#{role}' role."
238
+ message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
204
239
  else
205
- message = "No connection pool for '#{spec_name}' found."
240
+ message = "No connection pool for '#{connection_name}' found."
206
241
  end
207
242
 
208
243
  raise ConnectionNotEstablished, message
@@ -213,36 +248,66 @@ module ActiveRecord
213
248
 
214
249
  # Returns true if a connection that's accessible to this class has
215
250
  # already been opened.
216
- def connected?(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
217
- pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
251
+ def connected?(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
252
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
218
253
  pool && pool.connected?
219
254
  end
220
255
 
221
- def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
222
- if pool_manager = get_pool_manager(owner)
223
- pool_config = pool_manager.remove_pool_config(role, shard)
224
-
225
- if pool_config
226
- pool_config.disconnect!
227
- pool_config.db_config
228
- end
256
+ def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
257
+ if pool_manager = get_pool_manager(connection_name)
258
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
229
259
  end
230
260
  end
231
261
 
232
- # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
262
+ # Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
233
263
  # This makes retrieving the connection pool O(1) once the process is warm.
234
264
  # When a connection is established or removed, we invalidate the cache.
235
- def retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
236
- pool_config = get_pool_manager(owner)&.get_pool_config(role, shard)
265
+ def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
266
+ pool_config = get_pool_manager(connection_name)&.get_pool_config(role, shard)
237
267
  pool_config&.pool
238
268
  end
239
269
 
240
270
  private
241
- attr_reader :owner_to_pool_manager
271
+ attr_reader :connection_name_to_pool_manager
242
272
 
243
- # Returns the pool manager for an owner.
244
- def get_pool_manager(owner)
245
- owner_to_pool_manager[owner]
273
+ # Returns the pool manager for a connection name / identifier.
274
+ def get_pool_manager(connection_name)
275
+ connection_name_to_pool_manager[connection_name]
276
+ end
277
+
278
+ # Get the existing pool manager or initialize and assign a new one.
279
+ def set_pool_manager(connection_name)
280
+ connection_name_to_pool_manager[connection_name] ||= PoolManager.new
281
+ end
282
+
283
+ def pool_managers
284
+ connection_name_to_pool_manager.values
285
+ end
286
+
287
+ def deprecation_for_pool_handling(method)
288
+ roles = []
289
+ pool_managers.each do |pool_manager|
290
+ roles << pool_manager.role_names
291
+ end
292
+
293
+ if roles.flatten.uniq.count > 1
294
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
295
+ `#{method}` currently only applies to connection pools in the current
296
+ role (`#{ActiveRecord::Base.current_role}`). In Rails 7.2, this method
297
+ will apply to all known pools, regardless of role. To affect only those
298
+ connections belonging to a specific role, pass the role name as an
299
+ argument. To switch to the new behavior, pass `:all` as the role name.
300
+ MSG
301
+ end
302
+ end
303
+
304
+ def disconnect_pool_from_pool_manager(pool_manager, role, shard)
305
+ pool_config = pool_manager.remove_pool_config(role, shard)
306
+
307
+ if pool_config
308
+ pool_config.disconnect!
309
+ pool_config.db_config
310
+ end
246
311
  end
247
312
 
248
313
  # Returns an instance of PoolConfig for a given adapter.
@@ -255,7 +320,7 @@ module ActiveRecord
255
320
  # pool_config.db_config.configuration_hash
256
321
  # # => { host: "localhost", database: "foo", adapter: "sqlite3" }
257
322
  #
258
- def resolve_pool_config(config, owner_name, role, shard)
323
+ def resolve_pool_config(config, connection_name, role, shard)
259
324
  db_config = Base.configurations.resolve(config)
260
325
 
261
326
  raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
@@ -285,7 +350,17 @@ module ActiveRecord
285
350
  raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
286
351
  end
287
352
 
288
- ConnectionAdapters::PoolConfig.new(owner_name, db_config, role, shard)
353
+ ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
354
+ end
355
+
356
+ def determine_owner_name(owner_name, config)
357
+ if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
358
+ StringConnectionName.new(owner_name.to_s)
359
+ elsif config.is_a?(Symbol)
360
+ StringConnectionName.new(config.to_s)
361
+ else
362
+ owner_name
363
+ end
289
364
  end
290
365
  end
291
366
  end
@@ -6,6 +6,8 @@ require "monitor"
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters
8
8
  class ConnectionPool
9
+ # = Active Record Connection Pool \Queue
10
+ #
9
11
  # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
10
12
  # with which it shares a Monitor.
11
13
  class Queue
@@ -6,12 +6,14 @@ require "weakref"
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters
8
8
  class ConnectionPool
9
+ # = Active Record Connection Pool \Reaper
10
+ #
9
11
  # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
10
12
  # +pool+. A reaper instantiated with a zero frequency will never reap
11
13
  # the connection pool.
12
14
  #
13
15
  # Configure the frequency by setting +reaping_frequency+ in your database
14
- # yaml file (default 60 seconds).
16
+ # YAML file (default 60 seconds).
15
17
  class Reaper
16
18
  attr_reader :pool, :frequency
17
19
 
@@ -10,35 +10,33 @@ 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
18
-
19
- def set_schema_cache(cache)
20
- self.schema_cache = cache
21
- end
22
-
23
- def lazily_set_schema_cache
24
- return unless ActiveRecord.lazily_load_schema_cache
25
-
26
- cache = SchemaCache.load_from(db_config.lazy_schema_cache_path)
27
- set_schema_cache(cache)
28
- end
29
13
  end
30
14
 
31
15
  class NullPool # :nodoc:
32
16
  include ConnectionAdapters::AbstractPool
33
17
 
34
- attr_accessor :schema_cache
18
+ class NullConfig # :nodoc:
19
+ def method_missing(*)
20
+ nil
21
+ end
22
+ end
23
+ NULL_CONFIG = NullConfig.new # :nodoc:
24
+
25
+ def schema_reflection
26
+ SchemaReflection.new(nil)
27
+ end
35
28
 
36
29
  def connection_class; end
37
30
  def checkin(_); end
38
31
  def remove(_); end
39
32
  def async_executor; end
33
+ def db_config
34
+ NULL_CONFIG
35
+ end
40
36
  end
41
37
 
38
+ # = Active Record Connection Pool
39
+ #
42
40
  # Connection pool base class for managing Active Record database
43
41
  # connections.
44
42
  #
@@ -53,7 +51,7 @@ module ActiveRecord
53
51
  # handle cases in which there are more threads than connections: if all
54
52
  # connections have been checked out, and a thread tries to checkout a
55
53
  # connection anyway, then ConnectionPool will wait until some other thread
56
- # has checked in a connection.
54
+ # has checked in a connection, or the +checkout_timeout+ has expired.
57
55
  #
58
56
  # == Obtaining (checking out) a connection
59
57
  #
@@ -64,7 +62,7 @@ module ActiveRecord
64
62
  # as with Active Record 2.1 and
65
63
  # earlier (pre-connection-pooling). Eventually, when you're done with
66
64
  # 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!].
65
+ # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
68
66
  # This will be the default behavior for Active Record when used in conjunction with
69
67
  # Action Pack's request handling cycle.
70
68
  # 2. Manually check out a connection from the pool with
@@ -78,6 +76,12 @@ module ActiveRecord
78
76
  # Connections in the pool are actually AbstractAdapter objects (or objects
79
77
  # compatible with AbstractAdapter's interface).
80
78
  #
79
+ # While a thread has a connection checked out from the pool using one of the
80
+ # above three methods, that connection will automatically be the one used
81
+ # by ActiveRecord queries executing on that thread. It is not required to
82
+ # explicitly pass the checked out connection to \Rails models or queries, for
83
+ # example.
84
+ #
81
85
  # == Options
82
86
  #
83
87
  # There are several connection-pooling-related options that you can add to
@@ -105,11 +109,9 @@ module ActiveRecord
105
109
  include ConnectionAdapters::AbstractPool
106
110
 
107
111
  attr_accessor :automatic_reconnect, :checkout_timeout
108
- attr_reader :db_config, :size, :reaper, :pool_config, :connection_class, :async_executor, :role, :shard
112
+ attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
109
113
 
110
- alias_method :connection_klass, :connection_class
111
- deprecate :connection_klass
112
- delegate :schema_cache, :schema_cache=, to: :pool_config
114
+ delegate :schema_reflection, :schema_reflection=, to: :pool_config
113
115
 
114
116
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
117
  # object which describes database connection information (e.g. adapter,
@@ -122,7 +124,6 @@ module ActiveRecord
122
124
 
123
125
  @pool_config = pool_config
124
126
  @db_config = pool_config.db_config
125
- @connection_class = pool_config.connection_class
126
127
  @role = pool_config.role
127
128
  @shard = pool_config.shard
128
129
 
@@ -158,18 +159,20 @@ module ActiveRecord
158
159
 
159
160
  @async_executor = build_async_executor
160
161
 
161
- lazily_set_schema_cache
162
-
163
162
  @reaper = Reaper.new(self, db_config.reaping_frequency)
164
163
  @reaper.run
165
164
  end
166
165
 
167
166
  def lock_thread=(lock_thread)
168
167
  if lock_thread
169
- @lock_thread = Thread.current
168
+ @lock_thread = ActiveSupport::IsolatedExecutionState.context
170
169
  else
171
170
  @lock_thread = nil
172
171
  end
172
+
173
+ if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
174
+ active_connection.lock_thread = @lock_thread
175
+ end
173
176
  end
174
177
 
175
178
  # Retrieve the connection associated with the current thread, or call
@@ -181,6 +184,12 @@ module ActiveRecord
181
184
  @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
182
185
  end
183
186
 
187
+ def connection_class # :nodoc:
188
+ pool_config.connection_class
189
+ end
190
+ alias :connection_klass :connection_class
191
+ deprecate :connection_klass, deprecator: ActiveRecord.deprecator
192
+
184
193
  # Returns true if there is an open connection being used for the current thread.
185
194
  #
186
195
  # This method only works for connections that have been obtained through
@@ -197,18 +206,23 @@ module ActiveRecord
197
206
  # This method only works for connections that have been obtained through
198
207
  # #connection or #with_connection methods, connections obtained through
199
208
  # #checkout will not be automatically released.
200
- def release_connection(owner_thread = Thread.current)
209
+ def release_connection(owner_thread = ActiveSupport::IsolatedExecutionState.context)
201
210
  if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
202
211
  checkin conn
203
212
  end
204
213
  end
205
214
 
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.
215
+ # Yields a connection from the connection pool to the block. If no connection
216
+ # is already checked out by the current thread, a connection will be checked
217
+ # out from the pool, yielded to the block, and then returned to the pool when
218
+ # the block is finished. If a connection has already been checked out on the
219
+ # current thread, such as via #connection or #with_connection, that existing
220
+ # connection will be the one yielded and it will not be returned to the pool
221
+ # automatically at the end of the block; it is expected that such an existing
222
+ # connection will be properly returned to the pool by the code that checked
223
+ # it out.
210
224
  def with_connection
211
- unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
225
+ unless conn = @thread_cached_conns[connection_cache_key(ActiveSupport::IsolatedExecutionState.context)]
212
226
  conn = connection
213
227
  fresh_connection = true
214
228
  end
@@ -338,7 +352,9 @@ module ActiveRecord
338
352
  # Raises:
339
353
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
340
354
  def checkout(checkout_timeout = @checkout_timeout)
341
- checkout_and_verify(acquire_connection(checkout_timeout))
355
+ connection = checkout_and_verify(acquire_connection(checkout_timeout))
356
+ connection.lock_thread = @lock_thread
357
+ connection
342
358
  end
343
359
 
344
360
  # Check-in a database connection back into the pool, indicating that you
@@ -355,6 +371,7 @@ module ActiveRecord
355
371
  conn.expire
356
372
  end
357
373
 
374
+ conn.lock_thread = nil
358
375
  @available.add conn
359
376
  end
360
377
  end
@@ -510,7 +527,7 @@ module ActiveRecord
510
527
  end
511
528
 
512
529
  def current_thread
513
- @lock_thread || Thread.current
530
+ @lock_thread || ActiveSupport::IsolatedExecutionState.context
514
531
  end
515
532
 
516
533
  # Take control of all existing connections so a "group" action such as
@@ -527,13 +544,13 @@ module ActiveRecord
527
544
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
528
545
  collected_conns = synchronize do
529
546
  # account for our own connections
530
- @connections.select { |conn| conn.owner == Thread.current }
547
+ @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
531
548
  end
532
549
 
533
550
  newly_checked_out = []
534
551
  timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
535
552
 
536
- @available.with_a_bias_for(Thread.current) do
553
+ @available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
537
554
  loop do
538
555
  synchronize do
539
556
  return if collected_conns.size == @connections.size && @now_connecting == 0
@@ -580,7 +597,7 @@ module ActiveRecord
580
597
 
581
598
  thread_report = []
582
599
  @connections.each do |conn|
583
- unless conn.owner == Thread.current
600
+ unless conn.owner == ActiveSupport::IsolatedExecutionState.context
584
601
  thread_report << "#{conn} is owned by #{conn.owner}"
585
602
  end
586
603
  end
@@ -653,9 +670,12 @@ module ActiveRecord
653
670
  alias_method :release, :remove_connection_from_thread_cache
654
671
 
655
672
  def new_connection
656
- Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
657
- conn.check_version
658
- end
673
+ connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
674
+ connection.pool = self
675
+ connection.check_version
676
+ connection
677
+ rescue ConnectionNotEstablished => ex
678
+ raise ex.set_pool(self)
659
679
  end
660
680
 
661
681
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
@@ -702,10 +722,10 @@ module ActiveRecord
702
722
 
703
723
  def checkout_and_verify(c)
704
724
  c._run_checkout_callbacks do
705
- c.verify!
725
+ c.clean!
706
726
  end
707
727
  c
708
- rescue
728
+ rescue Exception
709
729
  remove c
710
730
  c.disconnect!
711
731
  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