activerecord 7.1.3.4 → 7.2.0.beta2

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +514 -2126
  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 +9 -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 +4 -2
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +3 -3
  17. data/lib/active_record/associations/has_one_association.rb +2 -2
  18. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  19. data/lib/active_record/associations/join_dependency.rb +5 -7
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +34 -11
  27. data/lib/active_record/attribute_assignment.rb +1 -11
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  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 +7 -6
  34. data/lib/active_record/attribute_methods.rb +87 -58
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +14 -30
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -58
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +161 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +65 -60
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +33 -61
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +109 -77
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +59 -38
  77. data/lib/active_record/counter_cache.rb +23 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +30 -6
  84. data/lib/active_record/destroy_association_async_job.rb +1 -1
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +17 -2
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/scheme.rb +8 -4
  94. data/lib/active_record/enum.rb +11 -2
  95. data/lib/active_record/errors.rb +16 -11
  96. data/lib/active_record/explain.rb +13 -24
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +17 -4
  99. data/lib/active_record/gem_version.rb +3 -3
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +8 -7
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/marshalling.rb +1 -1
  107. data/lib/active_record/message_pack.rb +2 -2
  108. data/lib/active_record/migration/command_recorder.rb +2 -3
  109. data/lib/active_record/migration/compatibility.rb +11 -3
  110. data/lib/active_record/migration/default_strategy.rb +4 -5
  111. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  112. data/lib/active_record/migration.rb +85 -76
  113. data/lib/active_record/model_schema.rb +34 -69
  114. data/lib/active_record/nested_attributes.rb +11 -3
  115. data/lib/active_record/normalization.rb +3 -7
  116. data/lib/active_record/persistence.rb +32 -354
  117. data/lib/active_record/query_cache.rb +18 -6
  118. data/lib/active_record/query_logs.rb +15 -0
  119. data/lib/active_record/query_logs_formatter.rb +1 -1
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +52 -64
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +41 -44
  124. data/lib/active_record/reflection.rb +98 -37
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +3 -3
  127. data/lib/active_record/relation/calculations.rb +94 -61
  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/polymorphic_array_value.rb +6 -1
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +197 -44
  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 +500 -66
  139. data/lib/active_record/result.rb +32 -45
  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/signed_id.rb +11 -1
  146. data/lib/active_record/statement_cache.rb +7 -7
  147. data/lib/active_record/table_metadata.rb +1 -10
  148. data/lib/active_record/tasks/database_tasks.rb +70 -42
  149. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  152. data/lib/active_record/test_fixtures.rb +82 -91
  153. data/lib/active_record/testing/query_assertions.rb +121 -0
  154. data/lib/active_record/timestamp.rb +4 -2
  155. data/lib/active_record/token_for.rb +22 -12
  156. data/lib/active_record/touch_later.rb +1 -1
  157. data/lib/active_record/transaction.rb +131 -0
  158. data/lib/active_record/transactions.rb +70 -14
  159. data/lib/active_record/translation.rb +0 -2
  160. data/lib/active_record/type/serialized.rb +1 -3
  161. data/lib/active_record/type_caster/connection.rb +4 -4
  162. data/lib/active_record/validations/associated.rb +9 -3
  163. data/lib/active_record/validations/uniqueness.rb +14 -10
  164. data/lib/active_record/validations.rb +4 -1
  165. data/lib/active_record.rb +149 -40
  166. data/lib/arel/alias_predication.rb +1 -1
  167. data/lib/arel/collectors/bind.rb +2 -0
  168. data/lib/arel/collectors/composite.rb +7 -0
  169. data/lib/arel/collectors/sql_string.rb +1 -1
  170. data/lib/arel/collectors/substitute_binds.rb +1 -1
  171. data/lib/arel/nodes/binary.rb +0 -6
  172. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  173. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  174. data/lib/arel/nodes/node.rb +4 -3
  175. data/lib/arel/nodes/sql_literal.rb +7 -0
  176. data/lib/arel/nodes.rb +2 -2
  177. data/lib/arel/predications.rb +1 -1
  178. data/lib/arel/select_manager.rb +1 -1
  179. data/lib/arel/tree_manager.rb +8 -3
  180. data/lib/arel/update_manager.rb +2 -1
  181. data/lib/arel/visitors/dot.rb +1 -0
  182. data/lib/arel/visitors/mysql.rb +9 -4
  183. data/lib/arel/visitors/postgresql.rb +1 -12
  184. data/lib/arel/visitors/to_sql.rb +31 -17
  185. data/lib/arel.rb +7 -3
  186. metadata +17 -12
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/digest"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  # = Active Record Connection Adapters Transaction State
@@ -107,6 +109,7 @@ module ActiveRecord
107
109
  def initialize; end
108
110
  def state; end
109
111
  def closed?; true; end
112
+ alias_method :blank?, :closed?
110
113
  def open?; false; end
111
114
  def joinable?; false; end
112
115
  def add_record(record, _ = true); end
@@ -115,15 +118,21 @@ module ActiveRecord
115
118
  def dirty!; end
116
119
  def invalidated?; false; end
117
120
  def invalidate!; end
121
+ def materialized?; false; end
122
+ def before_commit; yield; end
123
+ def after_commit; yield; end
124
+ def after_rollback; end # noop
125
+ def uuid; Digest::UUID.nil_uuid; end
118
126
  end
119
127
 
120
- class Transaction # :nodoc:
128
+ class Transaction < ActiveRecord::Transaction # :nodoc:
121
129
  attr_reader :connection, :state, :savepoint_name, :isolation_level
122
130
  attr_accessor :written
123
131
 
124
132
  delegate :invalidate!, :invalidated?, to: :@state
125
133
 
126
134
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
135
+ super()
127
136
  @connection = connection
128
137
  @state = TransactionState.new
129
138
  @records = nil
@@ -133,7 +142,7 @@ module ActiveRecord
133
142
  @run_commit_callbacks = run_commit_callbacks
134
143
  @lazy_enrollment_records = nil
135
144
  @dirty = false
136
- @instrumenter = TransactionInstrumenter.new(connection: connection)
145
+ @instrumenter = TransactionInstrumenter.new(connection: connection, transaction: self)
137
146
  end
138
147
 
139
148
  def dirty!
@@ -190,66 +199,80 @@ module ActiveRecord
190
199
  end
191
200
 
192
201
  def rollback_records
193
- return unless records
194
-
195
- ite = unique_records
202
+ if records
203
+ begin
204
+ ite = unique_records
196
205
 
197
- instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
206
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
198
207
 
199
- run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
200
- record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
201
- end
202
- ensure
203
- ite&.each do |i|
204
- i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
208
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
209
+ record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
210
+ end
211
+ ensure
212
+ ite&.each do |i|
213
+ i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
214
+ end
215
+ end
205
216
  end
217
+
218
+ @callbacks&.each(&:after_rollback)
206
219
  end
207
220
 
208
221
  def before_commit_records
209
- return unless records
210
-
211
222
  if @run_commit_callbacks
212
- if ActiveRecord.before_committed_on_all_records
213
- ite = unique_records
223
+ if records
224
+ if ActiveRecord.before_committed_on_all_records
225
+ ite = unique_records
214
226
 
215
- instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
216
- candidates[record] = record
217
- end
227
+ instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
228
+ candidates[record] = record
229
+ end
218
230
 
219
- run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
220
- record.before_committed! if should_run_callbacks
231
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
232
+ record.before_committed! if should_run_callbacks
233
+ end
234
+ else
235
+ records.uniq.each(&:before_committed!)
221
236
  end
222
- else
223
- records.uniq.each(&:before_committed!)
224
237
  end
238
+
239
+ @callbacks&.each(&:before_commit)
225
240
  end
241
+ # Note: When @run_commit_callbacks is false #commit_records takes care of appending
242
+ # remaining callbacks to the parent transaction
226
243
  end
227
244
 
228
245
  def commit_records
229
- return unless records
230
-
231
- ite = unique_records
246
+ if records
247
+ begin
248
+ ite = unique_records
232
249
 
233
- if @run_commit_callbacks
234
- instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
250
+ if @run_commit_callbacks
251
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
235
252
 
236
- run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
237
- record.committed!(should_run_callbacks: should_run_callbacks)
238
- end
239
- else
240
- while record = ite.shift
241
- # if not running callbacks, only adds the record to the parent transaction
242
- connection.add_transaction_record(record)
253
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
254
+ record.committed!(should_run_callbacks: should_run_callbacks)
255
+ end
256
+ else
257
+ while record = ite.shift
258
+ # if not running callbacks, only adds the record to the parent transaction
259
+ connection.add_transaction_record(record)
260
+ end
261
+ end
262
+ ensure
263
+ ite&.each { |i| i.committed!(should_run_callbacks: false) }
243
264
  end
244
265
  end
245
- ensure
246
- ite&.each { |i| i.committed!(should_run_callbacks: false) }
266
+
267
+ if @run_commit_callbacks
268
+ @callbacks&.each(&:after_commit)
269
+ elsif @callbacks
270
+ connection.current_transaction.append_callbacks(@callbacks)
271
+ end
247
272
  end
248
273
 
249
274
  def full_rollback?; true; end
250
275
  def joinable?; @joinable; end
251
- def closed?; false; end
252
- def open?; !closed?; end
253
276
 
254
277
  private
255
278
  def unique_records
@@ -349,7 +372,7 @@ module ActiveRecord
349
372
 
350
373
  def rollback
351
374
  unless @state.invalidated?
352
- connection.rollback_to_savepoint(savepoint_name) if materialized?
375
+ connection.rollback_to_savepoint(savepoint_name) if materialized? && connection.active?
353
376
  end
354
377
  @state.rollback!
355
378
  @instrumenter.finish(:rollback) if materialized?
@@ -532,9 +555,7 @@ module ActiveRecord
532
555
  @connection.lock.synchronize do
533
556
  transaction = begin_transaction(isolation: isolation, joinable: joinable)
534
557
  begin
535
- ret = yield
536
- completed = true
537
- ret
558
+ yield transaction
538
559
  rescue Exception => error
539
560
  rollback_transaction
540
561
  after_failure_actions(transaction, error)
@@ -542,24 +563,8 @@ module ActiveRecord
542
563
  raise
543
564
  ensure
544
565
  unless error
545
- # In 7.1 we enforce timeout >= 0.4.0 which no longer use throw, so we can
546
- # go back to the original behavior of committing on non-local return.
547
- # If users are using throw, we assume it's not an error case.
548
- completed = true if ActiveRecord.commit_transaction_on_non_local_return
549
-
550
566
  if Thread.current.status == "aborting"
551
567
  rollback_transaction
552
- elsif !completed && transaction.written
553
- ActiveRecord.deprecator.warn(<<~EOW)
554
- A transaction is being rolled back because the transaction block was
555
- exited using `return`, `break` or `throw`.
556
- In Rails 7.2 this transaction will be committed instead.
557
- To opt-in to the new behavior now and suppress this warning
558
- you can set:
559
-
560
- Rails.application.config.active_record.commit_transaction_on_non_local_return = true
561
- EOW
562
- rollback_transaction
563
568
  else
564
569
  begin
565
570
  commit_transaction
@@ -590,7 +595,7 @@ module ActiveRecord
590
595
  end
591
596
 
592
597
  private
593
- NULL_TRANSACTION = NullTransaction.new
598
+ NULL_TRANSACTION = NullTransaction.new.freeze
594
599
 
595
600
  # Deallocate invalidated prepared statements outside of the transaction
596
601
  def after_failure_actions(transaction, error)
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  # and +:limit+ options, etc.
24
24
  #
25
25
  # All the concrete database adapters follow the interface laid down in this class.
26
- # {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling#connection] returns an AbstractAdapter object, which
26
+ # {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection] returns an AbstractAdapter object, which
27
27
  # you can use.
28
28
  #
29
29
  # Most of the methods in the adapter are useful during migrations. Most
@@ -49,8 +49,6 @@ module ActiveRecord
49
49
  return if value.eql?(@pool)
50
50
  @schema_cache = nil
51
51
  @pool = value
52
-
53
- @pool.schema_reflection.load!(self) if ActiveRecord.lazily_load_schema_cache
54
52
  end
55
53
 
56
54
  set_callback :checkin, :after, :enable_lazy_transactions!
@@ -136,7 +134,7 @@ module ActiveRecord
136
134
  @logger = ActiveRecord::Base.logger
137
135
 
138
136
  if deprecated_logger || deprecated_connection_options || deprecated_config
139
- raise ArgumentError, "when initializing an ActiveRecord adapter with a config hash, that should be the only argument"
137
+ raise ArgumentError, "when initializing an Active Record adapter with a config hash, that should be the only argument"
140
138
  end
141
139
  else
142
140
  # Soft-deprecated for now; we'll probably warn in future.
@@ -174,19 +172,13 @@ module ActiveRecord
174
172
  @verified = false
175
173
  end
176
174
 
177
- THREAD_LOCK = ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new
178
- private_constant :THREAD_LOCK
179
-
180
- FIBER_LOCK = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
181
- private_constant :FIBER_LOCK
182
-
183
175
  def lock_thread=(lock_thread) # :nodoc:
184
176
  @lock =
185
177
  case lock_thread
186
178
  when Thread
187
- THREAD_LOCK
179
+ ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new
188
180
  when Fiber
189
- FIBER_LOCK
181
+ ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
190
182
  else
191
183
  ActiveSupport::Concurrency::NullLock
192
184
  end
@@ -215,10 +207,6 @@ module ActiveRecord
215
207
  @config[:replica] || false
216
208
  end
217
209
 
218
- def use_metadata_table?
219
- @config.fetch(:use_metadata_table, true)
220
- end
221
-
222
210
  def connection_retries
223
211
  (@config[:connection_retries] || 1).to_i
224
212
  end
@@ -246,22 +234,6 @@ module ActiveRecord
246
234
  connection_class.current_preventing_writes
247
235
  end
248
236
 
249
- def migrations_paths # :nodoc:
250
- @config[:migrations_paths] || Migrator.migrations_paths
251
- end
252
-
253
- def migration_context # :nodoc:
254
- MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
255
- end
256
-
257
- def schema_migration # :nodoc:
258
- SchemaMigration.new(self)
259
- end
260
-
261
- def internal_metadata # :nodoc:
262
- InternalMetadata.new(self)
263
- end
264
-
265
237
  def prepared_statements?
266
238
  @prepared_statements && !prepared_statements_disabled_cache.include?(object_id)
267
239
  end
@@ -327,7 +299,7 @@ module ActiveRecord
327
299
  end
328
300
 
329
301
  def schema_cache
330
- @schema_cache ||= BoundSchemaReflection.new(@pool.schema_reflection, self)
302
+ @pool.schema_cache || (@schema_cache ||= BoundSchemaReflection.for_lone_connection(@pool.schema_reflection, self))
331
303
  end
332
304
 
333
305
  # this method must only be called while holding connection pool's mutex
@@ -580,7 +552,7 @@ module ActiveRecord
580
552
  end
581
553
 
582
554
  def return_value_after_insert?(column) # :nodoc:
583
- column.auto_incremented_by_db?
555
+ column.auto_populated?
584
556
  end
585
557
 
586
558
  def async_enabled? # :nodoc:
@@ -651,15 +623,6 @@ module ActiveRecord
651
623
  yield
652
624
  end
653
625
 
654
- # Override to check all foreign key constraints in a database.
655
- def all_foreign_keys_valid?
656
- check_all_foreign_keys_valid!
657
- true
658
- rescue ActiveRecord::StatementInvalid
659
- false
660
- end
661
- deprecate :all_foreign_keys_valid?, deprecator: ActiveRecord.deprecator
662
-
663
626
  # Override to check all foreign key constraints in a database.
664
627
  # The adapter should raise a +ActiveRecord::StatementInvalid+ if foreign key
665
628
  # constraints are not met.
@@ -668,6 +631,13 @@ module ActiveRecord
668
631
 
669
632
  # CONNECTION MANAGEMENT ====================================
670
633
 
634
+ # Checks whether the connection to the database was established. This doesn't
635
+ # include checking whether the database is actually capable of responding, i.e.
636
+ # whether the connection is stale.
637
+ def connected?
638
+ !@raw_connection.nil?
639
+ end
640
+
671
641
  # Checks whether the connection to the database is still active. This includes
672
642
  # checking whether the database is actually capable of responding, i.e. whether
673
643
  # the connection isn't stale.
@@ -711,13 +681,14 @@ module ActiveRecord
711
681
  end
712
682
  end
713
683
 
714
-
715
684
  # Disconnects from the database if already connected. Otherwise, this
716
685
  # method does nothing.
717
686
  def disconnect!
718
- clear_cache!(new_connection: true)
719
- reset_transaction
720
- @raw_connection_dirty = false
687
+ @lock.synchronize do
688
+ clear_cache!(new_connection: true)
689
+ reset_transaction
690
+ @raw_connection_dirty = false
691
+ end
721
692
  end
722
693
 
723
694
  # Immediately forget this connection ever existed. Unlike disconnect!,
@@ -773,19 +744,17 @@ module ActiveRecord
773
744
  # is no longer active, then this method will reconnect to the database.
774
745
  def verify!
775
746
  unless active?
776
- if @unconfigured_connection
777
- @lock.synchronize do
778
- if @unconfigured_connection
779
- @raw_connection = @unconfigured_connection
780
- @unconfigured_connection = nil
781
- configure_connection
782
- @verified = true
783
- return
784
- end
747
+ @lock.synchronize do
748
+ if @unconfigured_connection
749
+ @raw_connection = @unconfigured_connection
750
+ @unconfigured_connection = nil
751
+ configure_connection
752
+ @verified = true
753
+ return
785
754
  end
786
- end
787
755
 
788
- reconnect!(restore_transactions: true)
756
+ reconnect!(restore_transactions: true)
757
+ end
789
758
  end
790
759
 
791
760
  @verified = true
@@ -868,7 +837,7 @@ module ActiveRecord
868
837
  end
869
838
 
870
839
  def database_version # :nodoc:
871
- schema_cache.database_version
840
+ pool.server_version(self)
872
841
  end
873
842
 
874
843
  def check_version # :nodoc:
@@ -879,7 +848,7 @@ module ActiveRecord
879
848
  # numbered migration that has been executed, or 0 if no schema
880
849
  # information is present / the database is empty.
881
850
  def schema_version
882
- migration_context.current_version
851
+ pool.migration_context.current_version
883
852
  end
884
853
 
885
854
  class << self
@@ -1149,6 +1118,8 @@ module ActiveRecord
1149
1118
  statement_name: statement_name,
1150
1119
  async: async,
1151
1120
  connection: self,
1121
+ transaction: current_transaction.presence,
1122
+ row_count: 0,
1152
1123
  &block
1153
1124
  )
1154
1125
  rescue ActiveRecord::StatementInvalid => ex
@@ -1212,7 +1183,7 @@ module ActiveRecord
1212
1183
  #
1213
1184
  # This is an internal hook to make possible connection adapters to build
1214
1185
  # custom result objects with connection-specific data.
1215
- def build_result(columns:, rows:, column_types: {})
1186
+ def build_result(columns:, rows:, column_types: nil)
1216
1187
  ActiveRecord::Result.new(columns, rows, column_types)
1217
1188
  end
1218
1189
 
@@ -1224,6 +1195,7 @@ module ActiveRecord
1224
1195
  # Implementations may assume this method will only be called while
1225
1196
  # holding @lock (or from #initialize).
1226
1197
  def configure_connection
1198
+ check_version
1227
1199
  end
1228
1200
 
1229
1201
  def default_prepared_statements
@@ -170,6 +170,10 @@ module ActiveRecord
170
170
  true
171
171
  end
172
172
 
173
+ def supports_insert_returning?
174
+ mariadb? && database_version >= "10.5.0"
175
+ end
176
+
173
177
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
174
178
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
175
179
  end
@@ -214,7 +218,7 @@ module ActiveRecord
214
218
  update("SET FOREIGN_KEY_CHECKS = 0")
215
219
  yield
216
220
  ensure
217
- update("SET FOREIGN_KEY_CHECKS = #{old}")
221
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
218
222
  end
219
223
  end
220
224
 
@@ -225,12 +229,12 @@ module ActiveRecord
225
229
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
226
230
  # to write stuff in an abstract way without concerning ourselves about whether it
227
231
  # needs to be explicitly freed or not.
228
- def execute_and_free(sql, name = nil, async: false) # :nodoc:
232
+ def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
229
233
  sql = transform_query(sql)
230
234
  check_if_write_query(sql)
231
235
 
232
236
  mark_transaction_written_if_write(sql)
233
- yield raw_execute(sql, name, async: async)
237
+ yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
234
238
  end
235
239
 
236
240
  def begin_db_transaction # :nodoc:
@@ -336,7 +340,7 @@ module ActiveRecord
336
340
  schema_cache.clear_data_source_cache!(table_name.to_s)
337
341
  schema_cache.clear_data_source_cache!(new_name.to_s)
338
342
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
339
- rename_table_indexes(table_name, new_name)
343
+ rename_table_indexes(table_name, new_name, **options)
340
344
  end
341
345
 
342
346
  # Drops a table from the database.
@@ -635,27 +639,59 @@ module ActiveRecord
635
639
  end
636
640
 
637
641
  def build_insert_sql(insert) # :nodoc:
638
- sql = +"INSERT #{insert.into} #{insert.values_list}"
639
-
640
- if insert.skip_duplicates?
641
- no_op_column = quote_column_name(insert.keys.first)
642
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
643
- elsif insert.update_duplicates?
644
- sql << " ON DUPLICATE KEY UPDATE "
645
- if insert.raw_update_sql?
646
- sql << insert.raw_update_sql
647
- else
648
- sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
649
- sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
642
+ no_op_column = quote_column_name(insert.keys.first)
643
+
644
+ # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
645
+ # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
646
+ if supports_insert_raw_alias_syntax?
647
+ values_alias = quote_table_name("#{insert.model.table_name}_values")
648
+ sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
649
+
650
+ if insert.skip_duplicates?
651
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
652
+ elsif insert.update_duplicates?
653
+ if insert.raw_update_sql?
654
+ sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
655
+ else
656
+ sql << " ON DUPLICATE KEY UPDATE "
657
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
658
+ sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
659
+ end
660
+ end
661
+ else
662
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
663
+
664
+ if insert.skip_duplicates?
665
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
666
+ elsif insert.update_duplicates?
667
+ sql << " ON DUPLICATE KEY UPDATE "
668
+ if insert.raw_update_sql?
669
+ sql << insert.raw_update_sql
670
+ else
671
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
672
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
673
+ end
650
674
  end
651
675
  end
652
676
 
677
+ sql << " RETURNING #{insert.returning}" if insert.returning
653
678
  sql
654
679
  end
655
680
 
656
681
  def check_version # :nodoc:
657
682
  if database_version < "5.5.8"
658
- raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
683
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
684
+ end
685
+ end
686
+
687
+ #--
688
+ # QUOTING ==================================================
689
+ #++
690
+
691
+ # Quotes strings for use in SQL input.
692
+ def quote_string(string)
693
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
694
+ connection.escape(string)
659
695
  end
660
696
  end
661
697
 
@@ -734,7 +770,11 @@ module ActiveRecord
734
770
  return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
735
771
 
736
772
  @affected_rows_before_warnings = @raw_connection.affected_rows
773
+ warning_count = @raw_connection.warning_count
737
774
  result = @raw_connection.query("SHOW WARNINGS")
775
+ result = [
776
+ ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
777
+ ] if result.count == 0
738
778
  result.each do |level, code, message|
739
779
  warning = SQLWarning.new(message, code, level, sql, @pool)
740
780
  next if warning_ignored?(warning)
@@ -756,6 +796,7 @@ module ActiveRecord
756
796
  ER_DB_CREATE_EXISTS = 1007
757
797
  ER_FILSORT_ABORT = 1028
758
798
  ER_DUP_ENTRY = 1062
799
+ ER_SERVER_SHUTDOWN = 1053
759
800
  ER_NOT_NULL_VIOLATION = 1048
760
801
  ER_NO_REFERENCED_ROW = 1216
761
802
  ER_ROW_IS_REFERENCED = 1217
@@ -784,7 +825,7 @@ module ActiveRecord
784
825
  else
785
826
  super
786
827
  end
787
- when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
828
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
788
829
  ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
789
830
  when ER_DB_CREATE_EXISTS
790
831
  DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
@@ -853,6 +894,10 @@ module ActiveRecord
853
894
  "DROP INDEX #{quote_column_name(index_name)}"
854
895
  end
855
896
 
897
+ def supports_insert_raw_alias_syntax?
898
+ !mariadb? && database_version >= "8.0.19"
899
+ end
900
+
856
901
  def supports_rename_index?
857
902
  if mariadb?
858
903
  database_version >= "10.5.2"
@@ -870,6 +915,7 @@ module ActiveRecord
870
915
  end
871
916
 
872
917
  def configure_connection
918
+ super
873
919
  variables = @config.fetch(:variables, {}).stringify_keys
874
920
 
875
921
  # Increase timeout so the server doesn't disconnect us.
@@ -977,7 +1023,11 @@ module ActiveRecord
977
1023
  end
978
1024
 
979
1025
  def version_string(full_version_string)
980
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
1026
+ if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
1027
+ matches[1]
1028
+ else
1029
+ raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
1030
+ end
981
1031
  end
982
1032
  end
983
1033
  end
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
 
12
12
  # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
13
13
  # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
14
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
14
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)", retryable: true).freeze # :nodoc:
15
15
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
16
16
 
17
17
  def write_query?(sql) # :nodoc:
@@ -55,6 +55,14 @@ module ActiveRecord
55
55
  super unless column.auto_increment?
56
56
  end
57
57
 
58
+ def returning_column_values(result)
59
+ if supports_insert_returning?
60
+ result.rows.first
61
+ else
62
+ super
63
+ end
64
+ end
65
+
58
66
  def combine_multi_statements(total_sql)
59
67
  total_sql.each_with_object([]) do |sql, total_sql_chunks|
60
68
  previous_packet = total_sql_chunks.last