activerecord 7.1.4.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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +643 -2274
  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 +14 -7
  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 +7 -1
  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 +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -4
  21. data/lib/active_record/associations/nested_error.rb +47 -0
  22. data/lib/active_record/associations/preloader/association.rb +2 -1
  23. data/lib/active_record/associations/preloader/branch.rb +7 -1
  24. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  25. data/lib/active_record/associations/singular_association.rb +6 -0
  26. data/lib/active_record/associations/through_association.rb +1 -1
  27. data/lib/active_record/associations.rb +59 -292
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  34. data/lib/active_record/attribute_methods.rb +54 -63
  35. data/lib/active_record/attributes.rb +61 -47
  36. data/lib/active_record/autosave_association.rb +12 -29
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/callbacks.rb +1 -1
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -65
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -74
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  45. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +15 -6
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
  50. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  51. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  52. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  53. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  55. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  57. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  59. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  61. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  62. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +17 -11
  63. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  64. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  65. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  66. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  67. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  68. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  71. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  72. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -75
  73. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  74. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  75. data/lib/active_record/connection_adapters.rb +121 -0
  76. data/lib/active_record/connection_handling.rb +56 -41
  77. data/lib/active_record/core.rb +86 -38
  78. data/lib/active_record/counter_cache.rb +18 -9
  79. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  80. data/lib/active_record/database_configurations/database_config.rb +19 -4
  81. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  82. data/lib/active_record/database_configurations/url_config.rb +20 -1
  83. data/lib/active_record/database_configurations.rb +1 -1
  84. data/lib/active_record/delegated_type.rb +24 -0
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +18 -3
  89. data/lib/active_record/encryption/key_provider.rb +1 -1
  90. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  91. data/lib/active_record/encryption/message_serializer.rb +4 -0
  92. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  94. data/lib/active_record/encryption.rb +2 -0
  95. data/lib/active_record/enum.rb +19 -2
  96. data/lib/active_record/errors.rb +46 -20
  97. data/lib/active_record/explain.rb +13 -24
  98. data/lib/active_record/fixtures.rb +37 -31
  99. data/lib/active_record/future_result.rb +8 -4
  100. data/lib/active_record/gem_version.rb +2 -2
  101. data/lib/active_record/inheritance.rb +4 -2
  102. data/lib/active_record/insert_all.rb +18 -15
  103. data/lib/active_record/integration.rb +4 -1
  104. data/lib/active_record/internal_metadata.rb +48 -34
  105. data/lib/active_record/locking/optimistic.rb +7 -6
  106. data/lib/active_record/log_subscriber.rb +0 -21
  107. data/lib/active_record/marshalling.rb +4 -1
  108. data/lib/active_record/message_pack.rb +1 -1
  109. data/lib/active_record/migration/command_recorder.rb +2 -3
  110. data/lib/active_record/migration/compatibility.rb +5 -3
  111. data/lib/active_record/migration/default_strategy.rb +4 -5
  112. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  113. data/lib/active_record/migration.rb +85 -76
  114. data/lib/active_record/model_schema.rb +32 -68
  115. data/lib/active_record/nested_attributes.rb +24 -5
  116. data/lib/active_record/normalization.rb +3 -7
  117. data/lib/active_record/persistence.rb +30 -352
  118. data/lib/active_record/query_cache.rb +19 -8
  119. data/lib/active_record/query_logs.rb +15 -0
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +42 -57
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +40 -43
  124. data/lib/active_record/reflection.rb +98 -36
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +14 -8
  127. data/lib/active_record/relation/calculations.rb +96 -63
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +224 -58
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +496 -72
  139. data/lib/active_record/result.rb +31 -44
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/scoping/named.rb +1 -0
  146. data/lib/active_record/signed_id.rb +20 -1
  147. data/lib/active_record/statement_cache.rb +7 -7
  148. data/lib/active_record/table_metadata.rb +1 -10
  149. data/lib/active_record/tasks/database_tasks.rb +81 -42
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  152. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  153. data/lib/active_record/test_fixtures.rb +86 -89
  154. data/lib/active_record/testing/query_assertions.rb +121 -0
  155. data/lib/active_record/timestamp.rb +2 -2
  156. data/lib/active_record/token_for.rb +22 -12
  157. data/lib/active_record/touch_later.rb +1 -1
  158. data/lib/active_record/transaction.rb +132 -0
  159. data/lib/active_record/transactions.rb +70 -14
  160. data/lib/active_record/translation.rb +0 -2
  161. data/lib/active_record/type/serialized.rb +1 -3
  162. data/lib/active_record/type_caster/connection.rb +4 -4
  163. data/lib/active_record/validations/associated.rb +9 -3
  164. data/lib/active_record/validations/uniqueness.rb +15 -10
  165. data/lib/active_record/validations.rb +4 -1
  166. data/lib/active_record.rb +148 -39
  167. data/lib/arel/alias_predication.rb +1 -1
  168. data/lib/arel/collectors/bind.rb +2 -0
  169. data/lib/arel/collectors/composite.rb +7 -0
  170. data/lib/arel/collectors/sql_string.rb +1 -1
  171. data/lib/arel/collectors/substitute_binds.rb +1 -1
  172. data/lib/arel/nodes/binary.rb +0 -6
  173. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  174. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  175. data/lib/arel/nodes/node.rb +4 -3
  176. data/lib/arel/nodes/sql_literal.rb +7 -0
  177. data/lib/arel/nodes.rb +2 -2
  178. data/lib/arel/predications.rb +1 -1
  179. data/lib/arel/select_manager.rb +1 -1
  180. data/lib/arel/tree_manager.rb +3 -2
  181. data/lib/arel/update_manager.rb +2 -1
  182. data/lib/arel/visitors/dot.rb +1 -0
  183. data/lib/arel/visitors/mysql.rb +9 -4
  184. data/lib/arel/visitors/postgresql.rb +1 -12
  185. data/lib/arel/visitors/sqlite.rb +25 -0
  186. data/lib/arel/visitors/to_sql.rb +29 -16
  187. data/lib/arel.rb +7 -3
  188. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  189. metadata +18 -12
@@ -4,20 +4,12 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module Trilogy
6
6
  module DatabaseStatements
7
- def select_all(*, **) # :nodoc:
8
- result = super
9
- with_raw_connection do |conn|
10
- conn.next_result while conn.more_results_exist?
11
- end
12
- result
13
- end
14
-
15
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
7
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
16
8
  sql = transform_query(sql)
17
9
  check_if_write_query(sql)
18
10
  mark_transaction_written_if_write(sql)
19
11
 
20
- result = raw_execute(sql, name, async: async)
12
+ result = raw_execute(sql, name, async: async, allow_retry: allow_retry)
21
13
  ActiveRecord::Result.new(result.fields, result.to_a)
22
14
  end
23
15
 
@@ -26,7 +18,8 @@ module ActiveRecord
26
18
  check_if_write_query(sql)
27
19
  mark_transaction_written_if_write(sql)
28
20
 
29
- raw_execute(to_sql(sql, binds), name)
21
+ sql, _binds = sql_for_insert(sql, pk, binds, returning)
22
+ raw_execute(sql, name)
30
23
  end
31
24
 
32
25
  def exec_delete(sql, name = nil, binds = []) # :nodoc:
@@ -42,19 +35,27 @@ module ActiveRecord
42
35
 
43
36
  private
44
37
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
45
- log(sql, name, async: async) do
38
+ log(sql, name, async: async) do |notification_payload|
46
39
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
47
40
  sync_timezone_changes(conn)
48
41
  result = conn.query(sql)
42
+ while conn.more_results_exist?
43
+ conn.next_result
44
+ end
49
45
  verified!
50
46
  handle_warnings(sql)
47
+ notification_payload[:row_count] = result.count
51
48
  result
52
49
  end
53
50
  end
54
51
  end
55
52
 
56
53
  def last_inserted_id(result)
57
- result.last_insert_id
54
+ if supports_insert_returning?
55
+ super
56
+ else
57
+ result.last_insert_id
58
+ end
58
59
  end
59
60
 
60
61
  def sync_timezone_changes(conn)
@@ -71,7 +72,6 @@ module ActiveRecord
71
72
  combine_multi_statements(statements).each do |statement|
72
73
  with_raw_connection do |conn|
73
74
  raw_execute(statement, name)
74
- conn.next_result while conn.more_results_exist?
75
75
  end
76
76
  end
77
77
  end
@@ -90,7 +90,7 @@ module ActiveRecord
90
90
 
91
91
  yield
92
92
  ensure
93
- conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
93
+ conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF) if active?
94
94
  end
95
95
  end
96
96
  end
@@ -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,16 +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
77
+
78
+ # Trilogy ignores `socket` if `host is set. We want the opposite to allow
79
+ # configuring UNIX domain sockets via `DATABASE_URL`.
80
+ config.delete(:host) if config[:socket]
104
81
 
105
- if @config[:prepared_statements]
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]
106
87
  raise ArgumentError, "Trilogy currently doesn't support prepared statements. Remove `prepared_statements: true` from your database configuration."
107
88
  end
108
89
 
109
- # Trilogy ignore `socket` if `host is set. We want the opposite to allow
110
- # configuring UNIX domain sockets via `DATABASE_URL`.
111
- @config.delete(:host) if @config[:socket]
90
+ super
112
91
  end
113
92
 
114
93
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
@@ -137,14 +116,12 @@ module ActiveRecord
137
116
  true
138
117
  end
139
118
 
140
- def quote_string(string)
141
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
142
- conn.escape(string)
143
- end
119
+ def connected?
120
+ !(@raw_connection.nil? || @raw_connection.closed?)
144
121
  end
145
122
 
146
123
  def active?
147
- !(@raw_connection.nil? || @raw_connection.closed?) && @lock.synchronize { @raw_connection&.ping } || false
124
+ connected? && @lock.synchronize { @raw_connection&.ping } || false
148
125
  rescue ::Trilogy::Error
149
126
  false
150
127
  end
@@ -206,7 +183,7 @@ module ActiveRecord
206
183
  end
207
184
 
208
185
  def full_version
209
- schema_cache.database_version.full_version_string
186
+ database_version.full_version_string
210
187
  end
211
188
 
212
189
  def get_full_version
@@ -219,18 +196,12 @@ module ActiveRecord
219
196
  if exception.is_a?(::Trilogy::TimeoutError) && !exception.error_code
220
197
  return ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
221
198
  end
222
- error_code = exception.error_code if exception.respond_to?(:error_code)
223
-
224
- case error_code
225
- when ER_SERVER_SHUTDOWN
226
- return ConnectionFailed.new(message, connection_pool: @pool)
227
- end
228
199
 
229
200
  case exception
230
- when Errno::EPIPE, SocketError, IOError
201
+ when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
231
202
  return ConnectionFailed.new(message, connection_pool: @pool)
232
203
  when ::Trilogy::Error
233
- 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")
234
205
  return ConnectionFailed.new(message, connection_pool: @pool)
235
206
  end
236
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
@@ -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
@@ -719,21 +736,28 @@ module ActiveRecord
719
736
  self.class.connection_handler
720
737
  end
721
738
 
722
- # 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!">"
723
749
  def inspect
724
- # We check defined?(@attributes) not to issue warnings if the object is
725
- # allocated but not initialized.
726
- inspection = if defined?(@attributes) && @attributes
727
- attribute_names.filter_map do |name|
728
- if _has_attribute?(name)
729
- "#{name}: #{attribute_for_inspect(name)}"
730
- end
731
- end.join(", ")
732
- else
733
- "not initialized"
734
- end
750
+ inspect_with_attributes(attributes_for_inspect)
751
+ end
735
752
 
736
- "#<#{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)
737
761
  end
738
762
 
739
763
  # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
@@ -741,17 +765,17 @@ module ActiveRecord
741
765
  def pretty_print(pp)
742
766
  return super if custom_inspect_method_defined?
743
767
  pp.object_address_group(self) do
744
- if defined?(@attributes) && @attributes
745
- 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) }
746
770
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
771
+ attr_name = attr_name.to_s
747
772
  pp.breakable " "
748
773
  pp.group(1) do
749
774
  pp.text attr_name
750
775
  pp.text ":"
751
776
  pp.breakable
752
- value = _read_attribute(attr_name)
753
- value = inspection_filter.filter_param(attr_name, value) unless value.nil?
754
- pp.pp value
777
+ value = attribute_for_inspect(attr_name)
778
+ pp.text value
755
779
  end
756
780
  end
757
781
  else
@@ -789,7 +813,6 @@ module ActiveRecord
789
813
  @strict_loading_mode = :all
790
814
 
791
815
  klass.define_attribute_methods
792
- klass.generate_alias_attributes
793
816
  end
794
817
 
795
818
  def initialize_internals_callback
@@ -809,5 +832,30 @@ module ActiveRecord
809
832
  def inspection_filter
810
833
  self.class.inspection_filter
811
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
812
860
  end
813
861
  end