activerecord 7.0.8.1 → 7.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +642 -1925
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +3 -3
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +59 -17
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. 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
  #
@@ -53,10 +55,7 @@ module ActiveRecord
53
55
  # about the model. The model needs to pass a connection specification name to the handler,
54
56
  # in order to look up the correct connection pool.
55
57
  class ConnectionHandler
56
- FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
57
- private_constant :FINALIZER
58
-
59
- class StringConnectionOwner # :nodoc:
58
+ class StringConnectionName # :nodoc:
60
59
  attr_reader :name
61
60
 
62
61
  def initialize(name)
@@ -73,11 +72,8 @@ module ActiveRecord
73
72
  end
74
73
 
75
74
  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
-
79
- # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
80
- ObjectSpace.define_finalizer self, FINALIZER
75
+ # These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
76
+ @connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
81
77
  end
82
78
 
83
79
  def prevent_writes # :nodoc:
@@ -88,138 +84,167 @@ module ActiveRecord
88
84
  ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes
89
85
  end
90
86
 
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
87
  def connection_pool_names # :nodoc:
114
- owner_to_pool_manager.keys
88
+ connection_name_to_pool_manager.keys
115
89
  end
116
90
 
117
- def all_connection_pools
118
- owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
91
+ # Returns the pools for a connection handler and given role. If +:all+ is passed,
92
+ # all pools belonging to the connection handler will be returned.
93
+ def connection_pool_list(role = nil)
94
+ if role.nil? || role == :all
95
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
96
+ else
97
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
98
+ end
119
99
  end
100
+ alias :connection_pools :connection_pool_list
101
+
102
+ def each_connection_pool(role = nil, &block) # :nodoc:
103
+ role = nil if role == :all
104
+ return enum_for(__method__, role) unless block_given?
120
105
 
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) }
106
+ connection_name_to_pool_manager.each_value do |manager|
107
+ manager.each_pool_config(role) do |pool_config|
108
+ yield pool_config.pool
109
+ end
110
+ end
123
111
  end
124
- alias :connection_pools :connection_pool_list
125
112
 
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)
113
+ def establish_connection(config, owner_name: Base, role: Base.current_role, shard: Base.current_shard, clobber: false)
114
+ owner_name = determine_owner_name(owner_name, config)
128
115
 
129
116
  pool_config = resolve_pool_config(config, owner_name, role, shard)
130
117
  db_config = pool_config.db_config
131
118
 
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
119
+ pool_manager = set_pool_manager(pool_config.connection_name)
137
120
 
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
121
+ # If there is an existing pool with the same values as the pool_config
122
+ # don't remove the connection. Connections should only be removed if we are
123
+ # establishing a connection on a class that is already connected to a different
124
+ # configuration.
125
+ existing_pool_config = pool_manager.get_pool_config(role, shard)
145
126
 
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)
127
+ if !clobber && existing_pool_config && existing_pool_config.db_config == db_config
128
+ # Update the pool_config's connection class if it differs. This is used
129
+ # for ensuring that ActiveRecord::Base and the primary_abstract_class use
130
+ # the same pool. Without this granular swapping will not work correctly.
131
+ if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
132
+ existing_pool_config.connection_class = owner_name
133
+ end
153
134
 
154
- message_bus.instrument("!connection.active_record", payload) do
155
- pool_config.pool
135
+ existing_pool_config.pool
136
+ else
137
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
138
+ pool_manager.set_pool_config(role, shard, pool_config)
139
+
140
+ payload = {
141
+ connection_name: pool_config.connection_name,
142
+ role: role,
143
+ shard: shard,
144
+ config: db_config.configuration_hash
145
+ }
146
+
147
+ ActiveSupport::Notifications.instrumenter.instrument("!connection.active_record", payload) do
148
+ pool_config.pool
149
+ end
156
150
  end
157
151
  end
158
152
 
159
153
  # Returns true if there are any active connections among the connection
160
154
  # pools that the ConnectionHandler is managing.
161
- def active_connections?(role = ActiveRecord::Base.current_role)
162
- connection_pool_list(role).any?(&:active_connection?)
155
+ def active_connections?(role = nil)
156
+ each_connection_pool(role).any?(&:active_connection?)
163
157
  end
164
158
 
165
159
  # Returns any connections in use by the current thread back to the pool,
166
160
  # and also returns connections to the pool cached by threads that are no
167
161
  # longer alive.
168
- def clear_active_connections!(role = ActiveRecord::Base.current_role)
169
- connection_pool_list(role).each(&:release_connection)
162
+ def clear_active_connections!(role = nil)
163
+ each_connection_pool(role).each do |pool|
164
+ pool.release_connection
165
+ pool.disable_query_cache!
166
+ end
170
167
  end
171
168
 
172
169
  # Clears the cache which maps classes.
173
170
  #
174
171
  # 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!)
172
+ def clear_reloadable_connections!(role = nil)
173
+ each_connection_pool(role).each(&:clear_reloadable_connections!)
177
174
  end
178
175
 
179
- def clear_all_connections!(role = ActiveRecord::Base.current_role)
180
- connection_pool_list(role).each(&:disconnect!)
176
+ def clear_all_connections!(role = nil)
177
+ each_connection_pool(role).each(&:disconnect!)
181
178
  end
182
179
 
183
180
  # Disconnects all currently idle connections.
184
181
  #
185
182
  # See ConnectionPool#flush! for details.
186
- def flush_idle_connections!(role = ActiveRecord::Base.current_role)
187
- connection_pool_list(role).each(&:flush!)
183
+ def flush_idle_connections!(role = nil)
184
+ each_connection_pool(role).each(&:flush!)
188
185
  end
189
186
 
190
187
  # Locate the connection of the nearest super class. This can be an
191
188
  # active or defined connection: if it is the latter, it will be
192
189
  # opened and set as the active connection for the class it was defined
193
190
  # 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)
191
+ def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
192
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard, strict: true)
193
+ pool.lease_connection
194
+ end
195
+
196
+ # Returns true if a connection that's accessible to this class has
197
+ # already been opened.
198
+ def connected?(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
199
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
200
+ pool && pool.connected?
201
+ end
196
202
 
197
- unless pool
203
+ def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
204
+ if pool_manager = get_pool_manager(connection_name)
205
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
206
+ end
207
+ end
208
+
209
+ # Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
210
+ # This makes retrieving the connection pool O(1) once the process is warm.
211
+ # When a connection is established or removed, we invalidate the cache.
212
+ def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
213
+ pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
214
+
215
+ if strict && !pool
198
216
  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."
217
+ message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
202
218
  elsif role != ActiveRecord::Base.default_role
203
- message = "No connection pool for '#{spec_name}' found for the '#{role}' role."
219
+ message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
204
220
  else
205
- message = "No connection pool for '#{spec_name}' found."
221
+ message = "No connection pool for '#{connection_name}' found."
206
222
  end
207
223
 
208
224
  raise ConnectionNotEstablished, message
209
225
  end
210
226
 
211
- pool.connection
227
+ pool
212
228
  end
213
229
 
214
- # Returns true if a connection that's accessible to this class has
215
- # 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)
218
- pool && pool.connected?
219
- end
230
+ private
231
+ attr_reader :connection_name_to_pool_manager
232
+
233
+ # Returns the pool manager for a connection name / identifier.
234
+ def get_pool_manager(connection_name)
235
+ connection_name_to_pool_manager[connection_name]
236
+ end
237
+
238
+ # Get the existing pool manager or initialize and assign a new one.
239
+ def set_pool_manager(connection_name)
240
+ connection_name_to_pool_manager[connection_name] ||= PoolManager.new
241
+ end
242
+
243
+ def pool_managers
244
+ connection_name_to_pool_manager.values
245
+ end
220
246
 
221
- def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
222
- if pool_manager = get_pool_manager(owner)
247
+ def disconnect_pool_from_pool_manager(pool_manager, role, shard)
223
248
  pool_config = pool_manager.remove_pool_config(role, shard)
224
249
 
225
250
  if pool_config
@@ -227,23 +252,6 @@ module ActiveRecord
227
252
  pool_config.db_config
228
253
  end
229
254
  end
230
- end
231
-
232
- # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
233
- # This makes retrieving the connection pool O(1) once the process is warm.
234
- # 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)
237
- pool_config&.pool
238
- end
239
-
240
- private
241
- attr_reader :owner_to_pool_manager
242
-
243
- # Returns the pool manager for an owner.
244
- def get_pool_manager(owner)
245
- owner_to_pool_manager[owner]
246
- end
247
255
 
248
256
  # Returns an instance of PoolConfig for a given adapter.
249
257
  # Accepts a hash one layer deep that contains all connection information.
@@ -255,37 +263,21 @@ module ActiveRecord
255
263
  # pool_config.db_config.configuration_hash
256
264
  # # => { host: "localhost", database: "foo", adapter: "sqlite3" }
257
265
  #
258
- def resolve_pool_config(config, owner_name, role, shard)
266
+ def resolve_pool_config(config, connection_name, role, shard)
259
267
  db_config = Base.configurations.resolve(config)
260
-
268
+ db_config.validate!
261
269
  raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
270
+ ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
271
+ end
262
272
 
263
- # Require the adapter itself and give useful feedback about
264
- # 1. Missing adapter gems and
265
- # 2. Adapter gems' missing dependencies.
266
- path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
267
- begin
268
- require path_to_adapter
269
- rescue LoadError => e
270
- # We couldn't require the adapter itself. Raise an exception that
271
- # points out config typos and missing gems.
272
- if e.path == path_to_adapter
273
- # We can assume that a non-builtin adapter was specified, so it's
274
- # either misspelled or missing from Gemfile.
275
- raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
276
-
277
- # Bubbled up from the adapter require. Prefix the exception message
278
- # with some guidance about how to address it and reraise.
279
- else
280
- raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
281
- end
282
- end
283
-
284
- unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
285
- raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
273
+ def determine_owner_name(owner_name, config)
274
+ if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
275
+ StringConnectionName.new(owner_name.to_s)
276
+ elsif config.is_a?(Symbol)
277
+ StringConnectionName.new(config.to_s)
278
+ else
279
+ owner_name
286
280
  end
287
-
288
- ConnectionAdapters::PoolConfig.new(owner_name, db_config, role, shard)
289
281
  end
290
282
  end
291
283
  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
 
@@ -41,6 +43,7 @@ module ActiveRecord
41
43
  # Advise multi-threaded app servers to ignore this thread for
42
44
  # the purposes of fork safety warnings
43
45
  Thread.current.thread_variable_set(:fork_safe, true)
46
+ Thread.current.name = "AR Pool Reaper"
44
47
  running = true
45
48
  while running
46
49
  sleep t