activerecord 7.0.4.3 → 7.1.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1657 -1274
  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 +20 -4
  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 +16 -10
  15. data/lib/active_record/associations/collection_proxy.rb +20 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  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 +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +31 -7
  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 +327 -222
  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 +150 -31
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +0 -4
  35. data/lib/active_record/attribute_methods/write.rb +3 -3
  36. data/lib/active_record/attribute_methods.rb +108 -26
  37. data/lib/active_record/attributes.rb +3 -3
  38. data/lib/active_record/autosave_association.rb +55 -9
  39. data/lib/active_record/base.rb +7 -2
  40. data/lib/active_record/callbacks.rb +16 -32
  41. data/lib/active_record/coders/column_serializer.rb +61 -0
  42. data/lib/active_record/coders/json.rb +1 -1
  43. data/lib/active_record/coders/yaml_column.rb +70 -42
  44. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  52. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +155 -25
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +290 -124
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -102
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -112
  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 -14
  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 +1 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
  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/money.rb +3 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +15 -8
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +362 -60
  83. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
  85. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  89. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -83
  92. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  93. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  94. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  95. data/lib/active_record/connection_adapters.rb +3 -1
  96. data/lib/active_record/connection_handling.rb +72 -95
  97. data/lib/active_record/core.rb +175 -153
  98. data/lib/active_record/counter_cache.rb +46 -25
  99. data/lib/active_record/database_configurations/database_config.rb +9 -3
  100. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  101. data/lib/active_record/database_configurations/url_config.rb +17 -11
  102. data/lib/active_record/database_configurations.rb +86 -33
  103. data/lib/active_record/delegated_type.rb +9 -4
  104. data/lib/active_record/deprecator.rb +7 -0
  105. data/lib/active_record/destroy_association_async_job.rb +2 -0
  106. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  107. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  108. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  109. data/lib/active_record/encryption/config.rb +25 -1
  110. data/lib/active_record/encryption/configurable.rb +12 -19
  111. data/lib/active_record/encryption/context.rb +10 -3
  112. data/lib/active_record/encryption/contexts.rb +5 -1
  113. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  114. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  115. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  116. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  117. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  118. data/lib/active_record/encryption/key_generator.rb +12 -1
  119. data/lib/active_record/encryption/message_serializer.rb +2 -0
  120. data/lib/active_record/encryption/properties.rb +3 -3
  121. data/lib/active_record/encryption/scheme.rb +19 -22
  122. data/lib/active_record/encryption.rb +1 -0
  123. data/lib/active_record/enum.rb +112 -28
  124. data/lib/active_record/errors.rb +112 -18
  125. data/lib/active_record/explain.rb +23 -3
  126. data/lib/active_record/explain_subscriber.rb +1 -1
  127. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  128. data/lib/active_record/fixture_set/render_context.rb +2 -0
  129. data/lib/active_record/fixture_set/table_row.rb +29 -8
  130. data/lib/active_record/fixtures.rb +135 -71
  131. data/lib/active_record/future_result.rb +31 -5
  132. data/lib/active_record/gem_version.rb +4 -4
  133. data/lib/active_record/inheritance.rb +30 -16
  134. data/lib/active_record/insert_all.rb +57 -10
  135. data/lib/active_record/integration.rb +8 -8
  136. data/lib/active_record/internal_metadata.rb +120 -30
  137. data/lib/active_record/locking/optimistic.rb +32 -18
  138. data/lib/active_record/locking/pessimistic.rb +5 -2
  139. data/lib/active_record/log_subscriber.rb +29 -12
  140. data/lib/active_record/marshalling.rb +56 -0
  141. data/lib/active_record/message_pack.rb +124 -0
  142. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  143. data/lib/active_record/middleware/database_selector.rb +9 -11
  144. data/lib/active_record/middleware/shard_selector.rb +3 -1
  145. data/lib/active_record/migration/command_recorder.rb +105 -7
  146. data/lib/active_record/migration/compatibility.rb +157 -58
  147. data/lib/active_record/migration/default_strategy.rb +23 -0
  148. data/lib/active_record/migration/execution_strategy.rb +19 -0
  149. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  150. data/lib/active_record/migration.rb +271 -114
  151. data/lib/active_record/model_schema.rb +64 -44
  152. data/lib/active_record/nested_attributes.rb +24 -6
  153. data/lib/active_record/normalization.rb +167 -0
  154. data/lib/active_record/persistence.rb +195 -42
  155. data/lib/active_record/promise.rb +84 -0
  156. data/lib/active_record/query_cache.rb +3 -21
  157. data/lib/active_record/query_logs.rb +77 -52
  158. data/lib/active_record/query_logs_formatter.rb +41 -0
  159. data/lib/active_record/querying.rb +15 -2
  160. data/lib/active_record/railtie.rb +109 -47
  161. data/lib/active_record/railties/controller_runtime.rb +14 -9
  162. data/lib/active_record/railties/databases.rake +142 -148
  163. data/lib/active_record/railties/job_runtime.rb +23 -0
  164. data/lib/active_record/readonly_attributes.rb +32 -5
  165. data/lib/active_record/reflection.rb +182 -44
  166. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  167. data/lib/active_record/relation/batches.rb +190 -61
  168. data/lib/active_record/relation/calculations.rb +232 -81
  169. data/lib/active_record/relation/delegation.rb +23 -9
  170. data/lib/active_record/relation/finder_methods.rb +77 -16
  171. data/lib/active_record/relation/merger.rb +2 -0
  172. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  173. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  174. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  175. data/lib/active_record/relation/predicate_builder.rb +26 -14
  176. data/lib/active_record/relation/query_attribute.rb +25 -1
  177. data/lib/active_record/relation/query_methods.rb +387 -71
  178. data/lib/active_record/relation/spawn_methods.rb +18 -1
  179. data/lib/active_record/relation.rb +91 -35
  180. data/lib/active_record/result.rb +25 -9
  181. data/lib/active_record/runtime_registry.rb +24 -1
  182. data/lib/active_record/sanitization.rb +51 -11
  183. data/lib/active_record/schema.rb +2 -3
  184. data/lib/active_record/schema_dumper.rb +50 -7
  185. data/lib/active_record/schema_migration.rb +68 -33
  186. data/lib/active_record/scoping/default.rb +15 -5
  187. data/lib/active_record/scoping/named.rb +2 -2
  188. data/lib/active_record/scoping.rb +2 -1
  189. data/lib/active_record/secure_password.rb +60 -0
  190. data/lib/active_record/secure_token.rb +21 -3
  191. data/lib/active_record/signed_id.rb +7 -5
  192. data/lib/active_record/store.rb +9 -9
  193. data/lib/active_record/suppressor.rb +3 -1
  194. data/lib/active_record/table_metadata.rb +16 -3
  195. data/lib/active_record/tasks/database_tasks.rb +127 -105
  196. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  197. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  198. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  199. data/lib/active_record/test_fixtures.rb +113 -96
  200. data/lib/active_record/timestamp.rb +27 -15
  201. data/lib/active_record/token_for.rb +113 -0
  202. data/lib/active_record/touch_later.rb +11 -6
  203. data/lib/active_record/transactions.rb +39 -13
  204. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  205. data/lib/active_record/type/internal/timezone.rb +7 -2
  206. data/lib/active_record/type/serialized.rb +8 -4
  207. data/lib/active_record/type/time.rb +4 -0
  208. data/lib/active_record/validations/absence.rb +1 -1
  209. data/lib/active_record/validations/numericality.rb +5 -4
  210. data/lib/active_record/validations/presence.rb +5 -28
  211. data/lib/active_record/validations/uniqueness.rb +47 -2
  212. data/lib/active_record/validations.rb +8 -4
  213. data/lib/active_record/version.rb +1 -1
  214. data/lib/active_record.rb +121 -16
  215. data/lib/arel/errors.rb +10 -0
  216. data/lib/arel/factory_methods.rb +4 -0
  217. data/lib/arel/filter_predications.rb +1 -1
  218. data/lib/arel/nodes/and.rb +4 -0
  219. data/lib/arel/nodes/binary.rb +6 -1
  220. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  221. data/lib/arel/nodes/cte.rb +36 -0
  222. data/lib/arel/nodes/filter.rb +1 -1
  223. data/lib/arel/nodes/fragments.rb +35 -0
  224. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  225. data/lib/arel/nodes/leading_join.rb +8 -0
  226. data/lib/arel/nodes/node.rb +111 -2
  227. data/lib/arel/nodes/sql_literal.rb +6 -0
  228. data/lib/arel/nodes/table_alias.rb +4 -0
  229. data/lib/arel/nodes.rb +4 -0
  230. data/lib/arel/predications.rb +2 -0
  231. data/lib/arel/table.rb +9 -5
  232. data/lib/arel/visitors/mysql.rb +8 -1
  233. data/lib/arel/visitors/to_sql.rb +81 -17
  234. data/lib/arel/visitors/visitor.rb +2 -2
  235. data/lib/arel.rb +16 -2
  236. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  237. data/lib/rails/generators/active_record/migration.rb +3 -1
  238. data/lib/rails/generators/active_record/model/USAGE +113 -0
  239. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  240. metadata +48 -12
  241. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  242. data/lib/active_record/null_relation.rb +0 -63
@@ -4,37 +4,83 @@ require "yaml"
4
4
 
5
5
  module ActiveRecord
6
6
  module Coders # :nodoc:
7
- class YAMLColumn # :nodoc:
8
- attr_accessor :object_class
9
-
10
- def initialize(attr_name, object_class = Object)
11
- @attr_name = attr_name
12
- @object_class = object_class
13
- check_arity_of_constructor
14
- end
7
+ class YAMLColumn < ColumnSerializer # :nodoc:
8
+ class SafeCoder
9
+ def initialize(permitted_classes: [], unsafe_load: nil)
10
+ @permitted_classes = permitted_classes
11
+ @unsafe_load = unsafe_load
12
+ end
15
13
 
16
- def dump(obj)
17
- return if obj.nil?
14
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("5.1")
15
+ def dump(object)
16
+ if @unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load
17
+ ::YAML.dump(object)
18
+ else
19
+ ::YAML.safe_dump(
20
+ object,
21
+ permitted_classes: @permitted_classes + ActiveRecord.yaml_column_permitted_classes,
22
+ aliases: true,
23
+ )
24
+ end
25
+ end
26
+ else
27
+ def dump(object)
28
+ YAML.dump(object)
29
+ end
30
+ end
18
31
 
19
- assert_valid_value(obj, action: "dump")
20
- YAML.dump obj
32
+ if YAML.respond_to?(:unsafe_load)
33
+ def load(payload)
34
+ if @unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load
35
+ YAML.unsafe_load(payload)
36
+ else
37
+ YAML.safe_load(
38
+ payload,
39
+ permitted_classes: @permitted_classes + ActiveRecord.yaml_column_permitted_classes,
40
+ aliases: true,
41
+ )
42
+ end
43
+ end
44
+ else
45
+ def load(payload)
46
+ if @unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load
47
+ YAML.load(payload)
48
+ else
49
+ YAML.safe_load(
50
+ payload,
51
+ permitted_classes: @permitted_classes + ActiveRecord.yaml_column_permitted_classes,
52
+ aliases: true,
53
+ )
54
+ end
55
+ end
56
+ end
21
57
  end
22
58
 
23
- def load(yaml)
24
- return object_class.new if object_class != Object && yaml.nil?
25
- return yaml unless yaml.is_a?(String) && yaml.start_with?("---")
26
- obj = yaml_load(yaml)
27
-
28
- assert_valid_value(obj, action: "load")
29
- obj ||= object_class.new if object_class != Object
59
+ def initialize(attr_name, object_class = Object, permitted_classes: [], unsafe_load: nil)
60
+ super(
61
+ attr_name,
62
+ SafeCoder.new(permitted_classes: permitted_classes || [], unsafe_load: unsafe_load),
63
+ object_class,
64
+ )
65
+ check_arity_of_constructor
66
+ end
30
67
 
31
- obj
68
+ def init_with(coder) # :nodoc:
69
+ unless coder["coder"]
70
+ permitted_classes = coder["permitted_classes"] || []
71
+ unsafe_load = coder["unsafe_load"] || false
72
+ coder["coder"] = SafeCoder.new(permitted_classes: permitted_classes, unsafe_load: unsafe_load)
73
+ end
74
+ super(coder)
32
75
  end
33
76
 
34
- def assert_valid_value(obj, action:)
35
- unless obj.nil? || obj.is_a?(object_class)
36
- raise SerializationTypeMismatch,
37
- "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
77
+ def coder
78
+ # This is to retain forward compatibility when loading records serialized with Marshal
79
+ # from a previous version of Rails.
80
+ @coder ||= begin
81
+ permitted_classes = defined?(@permitted_classes) ? @permitted_classes : []
82
+ unsafe_load = defined?(@unsafe_load) && @unsafe_load.nil?
83
+ SafeCoder.new(permitted_classes: permitted_classes, unsafe_load: unsafe_load)
38
84
  end
39
85
  end
40
86
 
@@ -44,24 +90,6 @@ module ActiveRecord
44
90
  rescue ArgumentError
45
91
  raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
46
92
  end
47
-
48
- if YAML.respond_to?(:unsafe_load)
49
- def yaml_load(payload)
50
- if ActiveRecord.use_yaml_unsafe_load
51
- YAML.unsafe_load(payload)
52
- else
53
- YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
54
- end
55
- end
56
- else
57
- def yaml_load(payload)
58
- if ActiveRecord.use_yaml_unsafe_load
59
- YAML.load(payload)
60
- else
61
- YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
62
- end
63
- end
64
- end
65
93
  end
66
94
  end
67
95
  end
@@ -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
  #
@@ -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