activerecord 7.1.3.4 → 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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +652 -2032
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +11 -5
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +3 -3
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/has_one_association.rb +2 -2
  20. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  21. data/lib/active_record/associations/join_dependency.rb +10 -12
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +2 -1
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +6 -0
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +62 -289
  29. data/lib/active_record/attribute_assignment.rb +0 -2
  30. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  31. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  32. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  33. data/lib/active_record/attribute_methods/read.rb +4 -16
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  36. data/lib/active_record/attribute_methods/write.rb +3 -3
  37. data/lib/active_record/attribute_methods.rb +89 -58
  38. data/lib/active_record/attributes.rb +61 -47
  39. data/lib/active_record/autosave_association.rb +17 -31
  40. data/lib/active_record/base.rb +2 -3
  41. data/lib/active_record/callbacks.rb +1 -1
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -58
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +190 -75
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  48. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +23 -10
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -19
  53. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  54. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  60. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  61. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  63. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +18 -12
  66. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  67. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  68. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  69. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  70. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  71. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  72. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  73. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  74. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
  76. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  77. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  78. data/lib/active_record/connection_adapters.rb +121 -0
  79. data/lib/active_record/connection_handling.rb +56 -41
  80. data/lib/active_record/core.rb +93 -40
  81. data/lib/active_record/counter_cache.rb +23 -10
  82. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  83. data/lib/active_record/database_configurations/database_config.rb +19 -4
  84. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  85. data/lib/active_record/database_configurations/url_config.rb +20 -1
  86. data/lib/active_record/database_configurations.rb +1 -1
  87. data/lib/active_record/delegated_type.rb +30 -6
  88. data/lib/active_record/destroy_association_async_job.rb +1 -1
  89. data/lib/active_record/dynamic_matchers.rb +2 -2
  90. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  91. data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
  92. data/lib/active_record/encryption/encryptor.rb +18 -3
  93. data/lib/active_record/encryption/key_provider.rb +1 -1
  94. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  95. data/lib/active_record/encryption/message_serializer.rb +4 -0
  96. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  97. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  98. data/lib/active_record/encryption/scheme.rb +8 -4
  99. data/lib/active_record/encryption.rb +2 -0
  100. data/lib/active_record/enum.rb +19 -2
  101. data/lib/active_record/errors.rb +46 -20
  102. data/lib/active_record/explain.rb +13 -24
  103. data/lib/active_record/fixtures.rb +37 -31
  104. data/lib/active_record/future_result.rb +17 -4
  105. data/lib/active_record/gem_version.rb +3 -3
  106. data/lib/active_record/inheritance.rb +4 -2
  107. data/lib/active_record/insert_all.rb +18 -15
  108. data/lib/active_record/integration.rb +4 -1
  109. data/lib/active_record/internal_metadata.rb +48 -34
  110. data/lib/active_record/locking/optimistic.rb +8 -7
  111. data/lib/active_record/log_subscriber.rb +0 -21
  112. data/lib/active_record/marshalling.rb +4 -1
  113. data/lib/active_record/message_pack.rb +2 -2
  114. data/lib/active_record/migration/command_recorder.rb +2 -3
  115. data/lib/active_record/migration/compatibility.rb +11 -3
  116. data/lib/active_record/migration/default_strategy.rb +4 -5
  117. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  118. data/lib/active_record/migration.rb +85 -76
  119. data/lib/active_record/model_schema.rb +38 -70
  120. data/lib/active_record/nested_attributes.rb +24 -5
  121. data/lib/active_record/normalization.rb +3 -7
  122. data/lib/active_record/persistence.rb +32 -354
  123. data/lib/active_record/query_cache.rb +19 -8
  124. data/lib/active_record/query_logs.rb +15 -0
  125. data/lib/active_record/query_logs_formatter.rb +1 -1
  126. data/lib/active_record/querying.rb +21 -9
  127. data/lib/active_record/railtie.rb +50 -68
  128. data/lib/active_record/railties/controller_runtime.rb +13 -4
  129. data/lib/active_record/railties/databases.rake +42 -45
  130. data/lib/active_record/reflection.rb +106 -38
  131. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  132. data/lib/active_record/relation/batches.rb +14 -8
  133. data/lib/active_record/relation/calculations.rb +96 -63
  134. data/lib/active_record/relation/delegation.rb +8 -11
  135. data/lib/active_record/relation/finder_methods.rb +16 -2
  136. data/lib/active_record/relation/merger.rb +4 -6
  137. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  138. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  139. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  140. data/lib/active_record/relation/predicate_builder.rb +3 -3
  141. data/lib/active_record/relation/query_methods.rb +245 -65
  142. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  143. data/lib/active_record/relation/spawn_methods.rb +2 -18
  144. data/lib/active_record/relation/where_clause.rb +7 -19
  145. data/lib/active_record/relation.rb +500 -66
  146. data/lib/active_record/result.rb +32 -45
  147. data/lib/active_record/runtime_registry.rb +39 -0
  148. data/lib/active_record/sanitization.rb +24 -19
  149. data/lib/active_record/schema.rb +8 -6
  150. data/lib/active_record/schema_dumper.rb +19 -9
  151. data/lib/active_record/schema_migration.rb +30 -14
  152. data/lib/active_record/scoping/named.rb +1 -0
  153. data/lib/active_record/signed_id.rb +20 -1
  154. data/lib/active_record/statement_cache.rb +7 -7
  155. data/lib/active_record/table_metadata.rb +1 -10
  156. data/lib/active_record/tasks/database_tasks.rb +98 -48
  157. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  158. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  159. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  160. data/lib/active_record/test_fixtures.rb +87 -89
  161. data/lib/active_record/testing/query_assertions.rb +121 -0
  162. data/lib/active_record/timestamp.rb +5 -3
  163. data/lib/active_record/token_for.rb +22 -12
  164. data/lib/active_record/touch_later.rb +1 -1
  165. data/lib/active_record/transaction.rb +132 -0
  166. data/lib/active_record/transactions.rb +70 -14
  167. data/lib/active_record/translation.rb +0 -2
  168. data/lib/active_record/type/serialized.rb +1 -3
  169. data/lib/active_record/type_caster/connection.rb +4 -4
  170. data/lib/active_record/validations/associated.rb +9 -3
  171. data/lib/active_record/validations/uniqueness.rb +15 -10
  172. data/lib/active_record/validations.rb +4 -1
  173. data/lib/active_record.rb +150 -41
  174. data/lib/arel/alias_predication.rb +1 -1
  175. data/lib/arel/collectors/bind.rb +2 -0
  176. data/lib/arel/collectors/composite.rb +7 -0
  177. data/lib/arel/collectors/sql_string.rb +1 -1
  178. data/lib/arel/collectors/substitute_binds.rb +1 -1
  179. data/lib/arel/nodes/binary.rb +0 -6
  180. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  181. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  182. data/lib/arel/nodes/node.rb +4 -3
  183. data/lib/arel/nodes/sql_literal.rb +7 -0
  184. data/lib/arel/nodes.rb +2 -2
  185. data/lib/arel/predications.rb +1 -1
  186. data/lib/arel/select_manager.rb +1 -1
  187. data/lib/arel/tree_manager.rb +8 -3
  188. data/lib/arel/update_manager.rb +2 -1
  189. data/lib/arel/visitors/dot.rb +1 -0
  190. data/lib/arel/visitors/mysql.rb +9 -4
  191. data/lib/arel/visitors/postgresql.rb +1 -12
  192. data/lib/arel/visitors/sqlite.rb +25 -0
  193. data/lib/arel/visitors/to_sql.rb +31 -17
  194. data/lib/arel.rb +7 -3
  195. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  196. metadata +21 -15
@@ -2,44 +2,17 @@
2
2
 
3
3
  require "active_record/connection_adapters/abstract_mysql_adapter"
4
4
 
5
- gem "trilogy", "~> 2.4"
5
+ gem "trilogy", "~> 2.7"
6
6
  require "trilogy"
7
7
 
8
8
  require "active_record/connection_adapters/trilogy/database_statements"
9
9
 
10
10
  module ActiveRecord
11
- module ConnectionHandling # :nodoc:
12
- def trilogy_adapter_class
13
- ConnectionAdapters::TrilogyAdapter
14
- end
15
-
16
- # Establishes a connection to the database that's used by all Active Record objects.
17
- def trilogy_connection(config)
18
- configuration = config.dup
19
-
20
- # Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
21
- # matched rather than number of rows updated.
22
- configuration[:found_rows] = true
23
-
24
- options = [
25
- configuration[:host],
26
- configuration[:port],
27
- configuration[:database],
28
- configuration[:username],
29
- configuration[:password],
30
- configuration[:socket],
31
- 0
32
- ]
33
-
34
- trilogy_adapter_class.new nil, logger, options, configuration
35
- end
36
- end
37
11
  module ConnectionAdapters
38
12
  class TrilogyAdapter < AbstractMysqlAdapter
39
13
  ER_BAD_DB_ERROR = 1049
40
14
  ER_DBACCESS_DENIED_ERROR = 1044
41
15
  ER_ACCESS_DENIED_ERROR = 1045
42
- ER_SERVER_SHUTDOWN = 1053
43
16
 
44
17
  ADAPTER_NAME = "Trilogy"
45
18
 
@@ -99,12 +72,22 @@ module ActiveRecord
99
72
  end
100
73
  end
101
74
 
102
- def initialize(...)
103
- super
75
+ def initialize(config, *)
76
+ config = config.dup
104
77
 
105
- # Trilogy ignore `socket` if `host is set. We want the opposite to allow
78
+ # Trilogy ignores `socket` if `host is set. We want the opposite to allow
106
79
  # configuring UNIX domain sockets via `DATABASE_URL`.
107
- @config.delete(:host) if @config[:socket]
80
+ config.delete(:host) if config[:socket]
81
+
82
+ # Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
83
+ # matched rather than number of rows updated.
84
+ config[:found_rows] = true
85
+
86
+ if config[:prepared_statements]
87
+ raise ArgumentError, "Trilogy currently doesn't support prepared statements. Remove `prepared_statements: true` from your database configuration."
88
+ end
89
+
90
+ super
108
91
  end
109
92
 
110
93
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
@@ -133,14 +116,12 @@ module ActiveRecord
133
116
  true
134
117
  end
135
118
 
136
- def quote_string(string)
137
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
138
- conn.escape(string)
139
- end
119
+ def connected?
120
+ !(@raw_connection.nil? || @raw_connection.closed?)
140
121
  end
141
122
 
142
123
  def active?
143
- connection&.ping || false
124
+ connected? && @lock.synchronize { @raw_connection&.ping } || false
144
125
  rescue ::Trilogy::Error
145
126
  false
146
127
  end
@@ -148,18 +129,18 @@ module ActiveRecord
148
129
  alias reset! reconnect!
149
130
 
150
131
  def disconnect!
151
- super
152
- unless connection.nil?
153
- connection.close
154
- self.connection = nil
132
+ @lock.synchronize do
133
+ super
134
+ @raw_connection&.close
135
+ @raw_connection = nil
155
136
  end
156
137
  end
157
138
 
158
139
  def discard!
159
- super
160
- unless connection.nil?
161
- connection.discard!
162
- self.connection = nil
140
+ @lock.synchronize do
141
+ super
142
+ @raw_connection&.discard!
143
+ @raw_connection = nil
163
144
  end
164
145
  end
165
146
 
@@ -189,28 +170,20 @@ module ActiveRecord
189
170
  exception.error_code if exception.respond_to?(:error_code)
190
171
  end
191
172
 
192
- def connection
193
- @raw_connection
194
- end
195
-
196
- def connection=(conn)
197
- @raw_connection = conn
198
- end
199
-
200
173
  def connect
201
- self.connection = self.class.new_client(@config)
174
+ @raw_connection = self.class.new_client(@config)
202
175
  rescue ConnectionNotEstablished => ex
203
176
  raise ex.set_pool(@pool)
204
177
  end
205
178
 
206
179
  def reconnect
207
- connection&.close
208
- self.connection = nil
180
+ @raw_connection&.close
181
+ @raw_connection = nil
209
182
  connect
210
183
  end
211
184
 
212
185
  def full_version
213
- schema_cache.database_version.full_version_string
186
+ database_version.full_version_string
214
187
  end
215
188
 
216
189
  def get_full_version
@@ -223,18 +196,12 @@ module ActiveRecord
223
196
  if exception.is_a?(::Trilogy::TimeoutError) && !exception.error_code
224
197
  return ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
225
198
  end
226
- error_code = exception.error_code if exception.respond_to?(:error_code)
227
-
228
- case error_code
229
- when ER_SERVER_SHUTDOWN
230
- return ConnectionFailed.new(message, connection_pool: @pool)
231
- end
232
199
 
233
200
  case exception
234
- when Errno::EPIPE, SocketError, IOError
201
+ when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
235
202
  return ConnectionFailed.new(message, connection_pool: @pool)
236
203
  when ::Trilogy::Error
237
- if /Connection reset by peer|TRILOGY_CLOSED_CONNECTION|TRILOGY_INVALID_SEQUENCE_ID|TRILOGY_UNEXPECTED_PACKET/.match?(exception.message)
204
+ if exception.is_a?(SystemCallError) || exception.message.include?("TRILOGY_INVALID_SEQUENCE_ID")
238
205
  return ConnectionFailed.new(message, connection_pool: @pool)
239
206
  end
240
207
  end
@@ -1,9 +1,130 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/string/filters"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  extend ActiveSupport::Autoload
6
8
 
9
+ @adapters = {}
10
+
11
+ class << self
12
+ # Registers a custom database adapter.
13
+ #
14
+ # Can also be used to define aliases.
15
+ #
16
+ # == Example
17
+ #
18
+ # ActiveRecord::ConnectionAdapters.register("megadb", "MegaDB::ActiveRecordAdapter", "mega_db/active_record_adapter")
19
+ #
20
+ # ActiveRecord::ConnectionAdapters.register("mysql", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter")
21
+ #
22
+ def register(name, class_name, path = class_name.underscore)
23
+ @adapters[name.to_s] = [class_name, path]
24
+ end
25
+
26
+ def resolve(adapter_name) # :nodoc:
27
+ # Require the adapter itself and give useful feedback about
28
+ # 1. Missing adapter gems.
29
+ # 2. Incorrectly registered adapters.
30
+ # 3. Adapter gems' missing dependencies.
31
+ class_name, path_to_adapter = @adapters[adapter_name.to_s]
32
+
33
+ unless class_name
34
+ # To provide better error messages for adapters expecting the pre-7.2 adapter registration API, we attempt
35
+ # to load the adapter file from the old location which was required by convention, and then raise an error
36
+ # describing how to upgrade the adapter to the new API.
37
+ legacy_adapter_path = "active_record/connection_adapters/#{adapter_name}_adapter"
38
+ legacy_adapter_connection_method_name = "#{adapter_name}_connection".to_sym
39
+
40
+ begin
41
+ require legacy_adapter_path
42
+ # If we reach here it means we found the found a file that may be the legacy adapter and should raise.
43
+ if ActiveRecord::ConnectionHandling.method_defined?(legacy_adapter_connection_method_name)
44
+ # If we find the connection method then we care certain it is a legacy adapter.
45
+ deprecation_message = <<~MSG.squish
46
+ Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
47
+ Rails 7.2 has changed the way Active Record database adapters are loaded. The adapter needs to be
48
+ updated to register itself rather than being loaded by convention.
49
+ Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
50
+ be modified.
51
+ See:
52
+ https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
53
+ MSG
54
+
55
+ exception_message = <<~MSG.squish
56
+ Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
57
+ Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
58
+ be modified.
59
+ MSG
60
+ else
61
+ # If we do not find the connection method we are much less certain it is a legacy adapter. Even though the
62
+ # file exists in the location defined by convenntion, it does not necessarily mean that file is supposed
63
+ # to define the adapter the legacy way. So raise an error that explains both possibilities.
64
+ deprecation_message = <<~MSG.squish
65
+ Database configuration specifies nonexistent '#{adapter_name}' adapter.
66
+ Available adapters are: #{@adapters.keys.sort.join(", ")}.
67
+ Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
68
+ adapter gem to your Gemfile if it's not in the list of available adapters.
69
+ Rails 7.2 has changed the way Active Record database adapters are loaded. Ensure that the adapter in
70
+ the Gemfile is at the latest version. If it is up to date, the adapter may need to be modified.
71
+ See:
72
+ https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
73
+ MSG
74
+
75
+ exception_message = <<~MSG.squish
76
+ Database configuration specifies nonexistent '#{adapter_name}' adapter.
77
+ Available adapters are: #{@adapters.keys.sort.join(", ")}.
78
+ Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
79
+ adapter gem to your Gemfile and that it is at its latest version. If it is up to date, the adapter may
80
+ need to be modified.
81
+ MSG
82
+ end
83
+
84
+ ActiveRecord.deprecator.warn(deprecation_message)
85
+ raise AdapterNotFound, exception_message
86
+ rescue LoadError => error
87
+ # The adapter was not found in the legacy location so fall through to the error handling for a missing adapter.
88
+ end
89
+
90
+ raise AdapterNotFound, <<~MSG.squish
91
+ Database configuration specifies nonexistent '#{adapter_name}' adapter.
92
+ Available adapters are: #{@adapters.keys.sort.join(", ")}.
93
+ Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
94
+ adapter gem to your Gemfile if it's not in the list of available adapters.
95
+ MSG
96
+ end
97
+
98
+ unless Object.const_defined?(class_name)
99
+ begin
100
+ require path_to_adapter
101
+ rescue LoadError => error
102
+ # We couldn't require the adapter itself.
103
+ if error.path == path_to_adapter
104
+ # We can assume here that a non-builtin adapter was specified and the path
105
+ # registered by the adapter gem is incorrect.
106
+ raise LoadError, "Error loading the '#{adapter_name}' Active Record adapter. Ensure that the path registered by the adapter gem is correct. #{error.message}", error.backtrace
107
+ else
108
+ # Bubbled up from the adapter require. Prefix the exception message
109
+ # with some guidance about how to address it and reraise.
110
+ raise LoadError, "Error loading the '#{adapter_name}' Active Record adapter. Missing a gem it depends on? #{error.message}", error.backtrace
111
+ end
112
+ end
113
+ end
114
+
115
+ begin
116
+ Object.const_get(class_name)
117
+ rescue NameError => error
118
+ raise AdapterNotFound, "Could not load the #{class_name} Active Record adapter (#{error.message})."
119
+ end
120
+ end
121
+ end
122
+
123
+ register "sqlite3", "ActiveRecord::ConnectionAdapters::SQLite3Adapter", "active_record/connection_adapters/sqlite3_adapter"
124
+ register "mysql2", "ActiveRecord::ConnectionAdapters::Mysql2Adapter", "active_record/connection_adapters/mysql2_adapter"
125
+ register "trilogy", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter"
126
+ register "postgresql", "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter", "active_record/connection_adapters/postgresql_adapter"
127
+
7
128
  eager_autoload do
8
129
  autoload :AbstractAdapter
9
130
  end
@@ -243,22 +243,64 @@ module ActiveRecord
243
243
  # Clears the query cache for all connections associated with the current thread.
244
244
  def clear_query_caches_for_current_thread
245
245
  connection_handler.each_connection_pool do |pool|
246
- pool.connection.clear_query_cache if pool.active_connection?
246
+ pool.clear_query_cache
247
247
  end
248
248
  end
249
249
 
250
250
  # Returns the connection currently associated with the class. This can
251
251
  # also be used to "borrow" the connection to do database work unrelated
252
252
  # to any of the specific Active Records.
253
+ # The connection will remain leased for the entire duration of the request
254
+ # or job, or until +#release_connection+ is called.
255
+ def lease_connection
256
+ connection_pool.lease_connection
257
+ end
258
+
259
+ # Soft deprecated. Use +#with_connection+ or +#lease_connection+ instead.
253
260
  def connection
254
- retrieve_connection
261
+ pool = connection_pool
262
+ if pool.permanent_lease?
263
+ case ActiveRecord.permanent_connection_checkout
264
+ when :deprecated
265
+ ActiveRecord.deprecator.warn <<~MESSAGE
266
+ Called deprecated `ActiveRecord::Base.connection` method.
267
+
268
+ Either use `with_connection` or `lease_connection`.
269
+ MESSAGE
270
+ when :disallowed
271
+ raise ActiveRecordError, <<~MESSAGE
272
+ Called deprecated `ActiveRecord::Base.connection` method.
273
+
274
+ Either use `with_connection` or `lease_connection`.
275
+ MESSAGE
276
+ end
277
+ pool.lease_connection
278
+ else
279
+ pool.active_connection
280
+ end
281
+ end
282
+
283
+ # Return the currently leased connection into the pool
284
+ def release_connection
285
+ connection_pool.release_connection
286
+ end
287
+
288
+ # Checkouts a connection from the pool, yield it and then check it back in.
289
+ # If a connection was already leased via #lease_connection or a parent call to
290
+ # #with_connection, that same connection is yieled.
291
+ # If #lease_connection is called inside the block, the connection won't be checked
292
+ # back in.
293
+ # If #connection is called inside the block, the connection won't be checked back in
294
+ # unless the +prevent_permanent_checkout+ argument is set to +true+.
295
+ def with_connection(prevent_permanent_checkout: false, &block)
296
+ connection_pool.with_connection(prevent_permanent_checkout: prevent_permanent_checkout, &block)
255
297
  end
256
298
 
257
299
  attr_writer :connection_specification_name
258
300
 
259
301
  # Returns the connection specification name from the current class or its parent.
260
302
  def connection_specification_name
261
- if !defined?(@connection_specification_name) || @connection_specification_name.nil?
303
+ if @connection_specification_name.nil?
262
304
  return self == Base ? Base.name : superclass.connection_specification_name
263
305
  end
264
306
  @connection_specification_name
@@ -279,8 +321,12 @@ module ActiveRecord
279
321
  connection_pool.db_config
280
322
  end
281
323
 
324
+ def adapter_class # :nodoc:
325
+ connection_pool.db_config.adapter_class
326
+ end
327
+
282
328
  def connection_pool
283
- connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished)
329
+ connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard, strict: true)
284
330
  end
285
331
 
286
332
  def retrieve_connection
@@ -292,16 +338,9 @@ module ActiveRecord
292
338
  connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
293
339
  end
294
340
 
295
- def remove_connection(name = nil)
296
- if name
297
- ActiveRecord.deprecator.warn(<<-MSG.squish)
298
- The name argument for `#remove_connection` is deprecated without replacement
299
- and will be removed in Rails 7.2. `#remove_connection` should always be called
300
- on the connection class directly, which makes the name argument obsolete.
301
- MSG
302
- end
341
+ def remove_connection
342
+ name = @connection_specification_name if defined?(@connection_specification_name)
303
343
 
304
- name ||= @connection_specification_name if defined?(@connection_specification_name)
305
344
  # if removing a connection that has a pool, we reset the
306
345
  # connection_specification_name so it will use the parent
307
346
  # pool.
@@ -312,39 +351,15 @@ module ActiveRecord
312
351
  connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
313
352
  end
314
353
 
315
- def clear_cache! # :nodoc:
316
- connection.schema_cache.clear!
317
- end
318
-
319
- def clear_active_connections!(role = nil)
320
- deprecation_for_delegation(__method__)
321
- connection_handler.clear_active_connections!(role)
322
- end
323
-
324
- def clear_reloadable_connections!(role = nil)
325
- deprecation_for_delegation(__method__)
326
- connection_handler.clear_reloadable_connections!(role)
354
+ def schema_cache # :nodoc:
355
+ connection_pool.schema_cache
327
356
  end
328
357
 
329
- def clear_all_connections!(role = nil)
330
- deprecation_for_delegation(__method__)
331
- connection_handler.clear_all_connections!(role)
332
- end
333
-
334
- def flush_idle_connections!(role = nil)
335
- deprecation_for_delegation(__method__)
336
- connection_handler.flush_idle_connections!(role)
358
+ def clear_cache! # :nodoc:
359
+ connection_pool.schema_cache.clear!
337
360
  end
338
361
 
339
362
  private
340
- def deprecation_for_delegation(method)
341
- ActiveRecord.deprecator.warn(<<-MSG.squish)
342
- Calling `ActiveRecord::Base.#{method} is deprecated. Please
343
- call the method directly on the connection handler; for
344
- example: `ActiveRecord::Base.connection_handler.#{method}`.
345
- MSG
346
- end
347
-
348
363
  def resolve_config_for_connection(config_or_env)
349
364
  raise "Anonymous class is not allowed." unless name
350
365
 
@@ -73,7 +73,7 @@ module ActiveRecord
73
73
  end
74
74
  self.configurations = {}
75
75
 
76
- # Returns fully resolved ActiveRecord::DatabaseConfigurations object
76
+ # Returns a fully resolved ActiveRecord::DatabaseConfigurations object.
77
77
  def self.configurations
78
78
  @@configurations
79
79
  end
@@ -102,6 +102,21 @@ module ActiveRecord
102
102
 
103
103
  class_attribute :shard_selector, instance_accessor: false, default: nil
104
104
 
105
+ ##
106
+ # :singleton-method:
107
+ #
108
+ # Specifies the attributes that will be included in the output of the
109
+ # #inspect method:
110
+ #
111
+ # Post.attributes_for_inspect = [:id, :title]
112
+ # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
113
+ #
114
+ # When set to `:all` inspect will list all the record's attributes:
115
+ #
116
+ # Post.attributes_for_inspect = :all
117
+ # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
118
+ class_attribute :attributes_for_inspect, instance_accessor: false, default: :all
119
+
105
120
  def self.application_record_class? # :nodoc:
106
121
  if ActiveRecord.application_record_class
107
122
  self == ActiveRecord.application_record_class
@@ -346,12 +361,12 @@ module ActiveRecord
346
361
 
347
362
  # Returns a string like 'Post(id:integer, title:string, body:text)'
348
363
  def inspect # :nodoc:
349
- if self == Base
364
+ if self == Base || singleton_class?
350
365
  super
351
366
  elsif abstract_class?
352
367
  "#{super}(abstract)"
353
- elsif !connected?
354
- "#{super} (call '#{super}.connection' to establish a connection)"
368
+ elsif !schema_loaded? && !connected?
369
+ "#{super} (call '#{super}.load_schema' to load schema informations)"
355
370
  elsif table_exists?
356
371
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
357
372
  "#{super}(#{attr_list})"
@@ -373,7 +388,7 @@ module ActiveRecord
373
388
  TypeCaster::Map.new(self)
374
389
  end
375
390
 
376
- def cached_find_by_statement(key, &block) # :nodoc:
391
+ def cached_find_by_statement(connection, key, &block) # :nodoc:
377
392
  cache = @find_by_statement_cache[connection.prepared_statements]
378
393
  cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
379
394
  end
@@ -396,8 +411,8 @@ module ActiveRecord
396
411
  @arel_table = nil
397
412
  @predicate_builder = nil
398
413
  @inspection_filter = nil
399
- @filter_attributes = nil
400
- @generated_association_methods = nil
414
+ @filter_attributes ||= nil
415
+ @generated_association_methods ||= nil
401
416
  end
402
417
  end
403
418
 
@@ -416,21 +431,23 @@ module ActiveRecord
416
431
  end
417
432
 
418
433
  def cached_find_by(keys, values)
419
- statement = cached_find_by_statement(keys) { |params|
420
- wheres = keys.index_with do |key|
421
- if key.is_a?(Array)
422
- [key.map { params.bind }]
423
- else
424
- params.bind
434
+ with_connection do |connection|
435
+ statement = cached_find_by_statement(connection, keys) { |params|
436
+ wheres = keys.index_with do |key|
437
+ if key.is_a?(Array)
438
+ [key.map { params.bind }]
439
+ else
440
+ params.bind
441
+ end
425
442
  end
426
- end
427
- where(wheres).limit(1)
428
- }
443
+ where(wheres).limit(1)
444
+ }
429
445
 
430
- begin
431
- statement.execute(values.flatten, connection).first
432
- rescue TypeError
433
- raise ActiveRecord::StatementInvalid
446
+ begin
447
+ statement.execute(values.flatten, connection, allow_retry: true).first
448
+ rescue TypeError
449
+ raise ActiveRecord::StatementInvalid
450
+ end
434
451
  end
435
452
  end
436
453
  end
@@ -450,7 +467,7 @@ module ActiveRecord
450
467
  init_internals
451
468
  initialize_internals_callback
452
469
 
453
- assign_attributes(attributes) if attributes
470
+ super
454
471
 
455
472
  yield self if block_given?
456
473
  _run_initialize_callbacks
@@ -701,6 +718,11 @@ module ActiveRecord
701
718
  @strict_loading_mode == :n_plus_one_only
702
719
  end
703
720
 
721
+ # Returns +true+ if the record uses strict_loading with +:all+ mode enabled.
722
+ def strict_loading_all?
723
+ @strict_loading_mode == :all
724
+ end
725
+
704
726
  # Marks this record as read only.
705
727
  #
706
728
  # customer = Customer.first
@@ -714,21 +736,28 @@ module ActiveRecord
714
736
  self.class.connection_handler
715
737
  end
716
738
 
717
- # Returns the contents of the record as a nicely formatted string.
739
+ # Returns the attributes of the record as a nicely formatted string.
740
+ #
741
+ # Post.first.inspect
742
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
743
+ #
744
+ # The attributes can be limited by setting <tt>.attributes_for_inspect</tt>.
745
+ #
746
+ # Post.attributes_for_inspect = [:id, :title]
747
+ # Post.first.inspect
748
+ # #=> "#<Post id: 1, title: "Hello, World!">"
718
749
  def inspect
719
- # We check defined?(@attributes) not to issue warnings if the object is
720
- # allocated but not initialized.
721
- inspection = if defined?(@attributes) && @attributes
722
- attribute_names.filter_map do |name|
723
- if _has_attribute?(name)
724
- "#{name}: #{attribute_for_inspect(name)}"
725
- end
726
- end.join(", ")
727
- else
728
- "not initialized"
729
- end
750
+ inspect_with_attributes(attributes_for_inspect)
751
+ end
730
752
 
731
- "#<#{self.class} #{inspection}>"
753
+ # Returns all attributes of the record as a nicely formatted string,
754
+ # ignoring <tt>.attributes_for_inspect</tt>.
755
+ #
756
+ # Post.first.full_inspect
757
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
758
+ #
759
+ def full_inspect
760
+ inspect_with_attributes(all_attributes_for_inspect)
732
761
  end
733
762
 
734
763
  # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
@@ -736,17 +765,17 @@ module ActiveRecord
736
765
  def pretty_print(pp)
737
766
  return super if custom_inspect_method_defined?
738
767
  pp.object_address_group(self) do
739
- if defined?(@attributes) && @attributes
740
- attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
768
+ if @attributes
769
+ attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) }
741
770
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
771
+ attr_name = attr_name.to_s
742
772
  pp.breakable " "
743
773
  pp.group(1) do
744
774
  pp.text attr_name
745
775
  pp.text ":"
746
776
  pp.breakable
747
- value = _read_attribute(attr_name)
748
- value = inspection_filter.filter_param(attr_name, value) unless value.nil?
749
- pp.pp value
777
+ value = attribute_for_inspect(attr_name)
778
+ pp.text value
750
779
  end
751
780
  end
752
781
  else
@@ -784,7 +813,6 @@ module ActiveRecord
784
813
  @strict_loading_mode = :all
785
814
 
786
815
  klass.define_attribute_methods
787
- klass.generate_alias_attributes
788
816
  end
789
817
 
790
818
  def initialize_internals_callback
@@ -804,5 +832,30 @@ module ActiveRecord
804
832
  def inspection_filter
805
833
  self.class.inspection_filter
806
834
  end
835
+
836
+ def inspect_with_attributes(attributes_to_list)
837
+ inspection = if @attributes
838
+ attributes_to_list.filter_map do |name|
839
+ name = name.to_s
840
+ if _has_attribute?(name)
841
+ "#{name}: #{attribute_for_inspect(name)}"
842
+ end
843
+ end.join(", ")
844
+ else
845
+ "not initialized"
846
+ end
847
+
848
+ "#<#{self.class} #{inspection}>"
849
+ end
850
+
851
+ def attributes_for_inspect
852
+ self.class.attributes_for_inspect == :all ? all_attributes_for_inspect : self.class.attributes_for_inspect
853
+ end
854
+
855
+ def all_attributes_for_inspect
856
+ return [] unless @attributes
857
+
858
+ attribute_names
859
+ end
807
860
  end
808
861
  end